第一次提交
This commit is contained in:
29
Plugins/GCS/Config/BaseGenericCombatSystem.ini
Normal file
29
Plugins/GCS/Config/BaseGenericCombatSystem.ini
Normal file
@@ -0,0 +1,29 @@
|
||||
[CoreRedirects]
|
||||
;GGA1.5 migration.
|
||||
+ClassRedirects = (OldName="/Script/GenericGameplayAbilities.GGA_AbilitySystemBPLibrary",NewName="/Script/GenericGameplayAbilities.GGA_AbilitySystemFunctionLibrary")
|
||||
+FunctionRedirects = (OldName="/Script/GenericGameplayAbilities.GGA_GameplayEffectContainerFunctionLibrary.MakeEffectContainerSpecFromContainerWithEventData",NewName="/Script/GenericGameplayAbilities.GGA_GameplayEffectContainerFunctionLibrary.MakeEffectContainerSpec")
|
||||
+FunctionRedirects = (OldName="/Script/GenericGameplayAttributes.GGA_AttributeSystemComponent.HandlePoseGameplayEffectExecute",NewName="/Script/GenericGameplayAttributes.GGA_AttributeSystemComponent.HandlePostGameplayEffectExecute")
|
||||
|
||||
|
||||
+PropertyRedirects = (OldName="/Script/GenericCombatSystem.GCS_TraceDefinition.DistanceTickInterval",NewName="/Script/GenericCombatSystem.GCS_TraceDefinition.DistanceTickThreshold")
|
||||
+EnumRedirects = (OldName="/Script/GenericCombatSystem.EGCS_AttackResultProcessorPolicy",ValueChanges=(("Always","Default")))
|
||||
+PropertyRedirects = (OldName="/Script/GenericCombatSystem.GCS_ComboDefinition.PayloadTag",NewName="/Script/GenericCombatSystem.GCS_ComboDefinition.EventTag")
|
||||
|
||||
;GCS1.5 migration
|
||||
+PropertyRedirects = (OldName="/Script/GenericCombatSystem.GCS_CollisionSystemComponent.OnTraceInstanceHitEvent",NewName="/Script/GenericCombatSystem.GCS_TraceSystemComponent.OnTraceHitEvent")
|
||||
+ClassRedirects = (OldName="/Script/GenericCombatSystem.GCS_CollisionSystemComponent",NewName="/Script/GenericCombatSystem.GCS_TraceSystemComponent")
|
||||
+FunctionRedirects = (OldName="/Script/GenericCombatSystem.GCS_TraceSystemComponent.GetCollisionSystemComponent",NewName="/Script/GenericCombatSystem.GCS_TraceSystemComponent.GetTraceSystemComponent")
|
||||
+FunctionRedirects = (OldName="/Script/GenericCombatSystem.GCS_TraceSystemComponent.FindCollisionSystemComponent",NewName="/Script/GenericCombatSystem.GCS_TraceSystemComponent.FindTraceSystemComponent")
|
||||
+StructRedirects = (OldName="/Script/GenericCombatSystem.GCS_CollisionTraceDefinition",NewName="/Script/GenericCombatSystem.GCS_TraceDefinition")
|
||||
+PackageRedirects = (OldName="/Game/GenericGame/CombatSystem/Core/Abilities/LightAttack/GA_GCS_SkillAttack",NewName="/Game/GenericGame/CombatSystem/Core/Abilities/Attack/GA_GCS_SkillAttack")
|
||||
+PackageRedirects = (OldName="GA_GCS_HoldAttack_C",NewName="GA_GCS_SkillAttack_Charged_C")
|
||||
+ClassRedirects = (OldName="/Game/GenericGame/CombatSystem/Core/BC_GCS_AttributeSystemComponent.BC_GCS_AttributeSystemComponent_C",NewName="/Game/GenericGame/CombatSystem/Core/BC_GCS_AttributeSystem.BC_GCS_AttributeSystem_C")
|
||||
+ClassRedirects = (OldName="/Game/GenericGame/CombatSystem/Core/BC_GCS_CombatComponent.BC_GCS_CombatComponent_C",NewName="/Game/GenericGame/CombatSystem/Core/BC_GCS_CombatSystem.BC_GCS_CombatSystem_C")
|
||||
+ClassRedirects = (OldName="/Game/GenericGame/CombatSystem/Core/BC_GCS_CombatCore.BC_GCS_CombatCore_C",NewName="/Game/GenericGame/CombatSystem/Core/BC_GCS_CombatEntity.BC_GCS_CombatEntity_C")
|
||||
+ClassRedirects = (OldName="/Game/GenericDemo/GCS/Blueprints/BC_DemoCombatCore.BC_DemoCombatCore_C",NewName="/Game/GenericDemo/GCS/Blueprints/BC_DemoCombatEntity.BC_DemoCombatEntity_C")
|
||||
+ClassRedirects = (OldName="/Script/GenericCombatSystem.GCS_CombatInterface", NewName="/Script/GenericCombatSystem.GCS_CombatEntityInterface")
|
||||
+FunctionRedirects = (OldName="/Script/GenericCombatSystem.GCS_CombatEntityInterface.GCS_SetWeapon",NewName="/Script/GenericCombatSystem.GCS_CombatEntityInterface.SetCurrentWeapon")
|
||||
+FunctionRedirects = (OldName="/Script/GenericCombatSystem.GCS_CombatEntityInterface.GCS_GetWeapon",NewName="/Script/GenericCombatSystem.GCS_CombatEntityInterface.GetCurrentWeapon")
|
||||
+FunctionRedirects = (OldName="/Script/GenericCombatSystem.GCS_CombatEntityInterface.GCS_GetRelativeTransformToSocket",NewName="/Script/GenericCombatSystem.GCS_CombatEntityInterface.GetRelativeTransformToSocket")
|
||||
+FunctionRedirects = (OldName="/Script/GenericCombatSystem.GCS_CombatEntityInterface.GetMovementInputDirection",NewName="/Script/GenericCombatSystem.GCS_CombatEntityInterface.GetMovementIntent")
|
||||
+FunctionRedirects = (OldName="/Script/GenericCombatSystem.GCS_CombatFunctionLibrary.GetCombatInterface",NewName="/Script/GenericCombatSystem.GCS_CombatFunctionLibrary.GetCombatEntityInterface")
|
||||
9
Plugins/GCS/Config/FilterPlugin.ini
Normal file
9
Plugins/GCS/Config/FilterPlugin.ini
Normal file
@@ -0,0 +1,9 @@
|
||||
[FilterPlugin]
|
||||
; This section lists additional files which will be packaged along with your plugin. Paths should be listed relative to the root plugin directory, and
|
||||
; may include "...", "*", and "?" wildcards to match directories, files, and individual characters respectively.
|
||||
;
|
||||
; Examples:
|
||||
; /README.txt
|
||||
; /Extras/...
|
||||
; /Binaries/ThirdParty/*.dll
|
||||
/Config/*
|
||||
96
Plugins/GCS/GenericCombatSystem.uplugin
Normal file
96
Plugins/GCS/GenericCombatSystem.uplugin
Normal file
@@ -0,0 +1,96 @@
|
||||
{
|
||||
"FileVersion": 3,
|
||||
"Version": 8,
|
||||
"VersionName": "1.5",
|
||||
"FriendlyName": "GenericCombatSystem",
|
||||
"Description": "Advanced GAS based Multiplayer combat framework.",
|
||||
"Category": "Gameplay",
|
||||
"CreatedBy": "YuewuDev",
|
||||
"CreatedByURL": "https://yuewu.dev/en",
|
||||
"DocsURL": "https://www.yuewu.dev/en/wiki",
|
||||
"MarketplaceURL": "com.epicgames.launcher://ue/Fab/product/d4d45c2c-c698-4274-bb14-5474b7880a01",
|
||||
"SupportURL": "https://discord.com/invite/xMRXAB2",
|
||||
"EngineVersion": "5.7.0",
|
||||
"CanContainContent": false,
|
||||
"Installed": true,
|
||||
"Modules": [
|
||||
{
|
||||
"Name": "GenericInputSystem",
|
||||
"Type": "Runtime",
|
||||
"LoadingPhase": "Default",
|
||||
"PlatformAllowList": [
|
||||
"Win64",
|
||||
"Android",
|
||||
"Linux"
|
||||
]
|
||||
},
|
||||
{
|
||||
"Name": "GenericGameplayAbilities",
|
||||
"Type": "Runtime",
|
||||
"LoadingPhase": "Default",
|
||||
"PlatformAllowList": [
|
||||
"Win64",
|
||||
"Android",
|
||||
"Linux"
|
||||
]
|
||||
},
|
||||
{
|
||||
"Name": "GenericGameplayAttributes",
|
||||
"Type": "Runtime",
|
||||
"LoadingPhase": "Default",
|
||||
"PlatformAllowList": [
|
||||
"Win64",
|
||||
"Android",
|
||||
"Linux"
|
||||
]
|
||||
},
|
||||
{
|
||||
"Name": "GenericGameplayAbilitiesEditor",
|
||||
"Type": "Editor",
|
||||
"LoadingPhase": "Default",
|
||||
"PlatformAllowList": [
|
||||
"Win64"
|
||||
]
|
||||
},
|
||||
{
|
||||
"Name": "GenericCombatSystem",
|
||||
"Type": "Runtime",
|
||||
"LoadingPhase": "Default",
|
||||
"PlatformAllowList": [
|
||||
"Win64",
|
||||
"Android",
|
||||
"Linux"
|
||||
]
|
||||
}
|
||||
],
|
||||
"Plugins": [
|
||||
{
|
||||
"Name": "EnhancedInput",
|
||||
"Enabled": true
|
||||
},
|
||||
{
|
||||
"Name": "GameplayAbilities",
|
||||
"Enabled": true
|
||||
},
|
||||
{
|
||||
"Name": "ModularGameplay",
|
||||
"Enabled": true
|
||||
},
|
||||
{
|
||||
"Name": "TargetingSystem",
|
||||
"Enabled": true
|
||||
},
|
||||
{
|
||||
"Name": "StateTree",
|
||||
"Enabled": true
|
||||
},
|
||||
{
|
||||
"Name": "GameplayStateTree",
|
||||
"Enabled": true
|
||||
},
|
||||
{
|
||||
"Name": "MotionWarping",
|
||||
"Enabled": true
|
||||
}
|
||||
]
|
||||
}
|
||||
BIN
Plugins/GCS/Resources/Icon128.png
Normal file
BIN
Plugins/GCS/Resources/Icon128.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
@@ -0,0 +1,55 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
using System.IO;
|
||||
using UnrealBuildTool;
|
||||
|
||||
public class GenericCombatSystem : ModuleRules
|
||||
{
|
||||
public GenericCombatSystem(ReadOnlyTargetRules Target) : base(Target)
|
||||
{
|
||||
PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs;
|
||||
|
||||
PublicIncludePaths.AddRange(
|
||||
new[]
|
||||
{
|
||||
Path.Combine(ModuleDirectory, "Public/AbilitySystem"),
|
||||
// ... add public include paths required here ...
|
||||
}
|
||||
);
|
||||
|
||||
PrivateIncludePaths.AddRange(
|
||||
new[]
|
||||
{
|
||||
Path.Combine(ModuleDirectory, "Public/AbilitySystem"),
|
||||
// ... add public include paths required here ...
|
||||
}
|
||||
);
|
||||
|
||||
PublicDependencyModuleNames.AddRange(
|
||||
new string[]
|
||||
{
|
||||
"Core",
|
||||
"GameplayTags",
|
||||
"GameplayTasks",
|
||||
"GameplayAbilities",
|
||||
"GenericGameplayAbilities",
|
||||
"GenericGameplayAttributes",
|
||||
"ModularGameplay",
|
||||
"TargetingSystem"
|
||||
}
|
||||
);
|
||||
|
||||
PrivateDependencyModuleNames.AddRange(
|
||||
new string[]
|
||||
{
|
||||
"CoreUObject",
|
||||
"NetCore",
|
||||
"Engine",
|
||||
"Niagara",
|
||||
"AIModule",
|
||||
"StateTreeModule",
|
||||
"DeveloperSettings"
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
|
||||
#include "Abilities/GCS_CombatAbility.h"
|
||||
|
||||
#include "GCS_CombatSystemComponent.h"
|
||||
#include "Utility/GCS_CombatFunctionLibrary.h"
|
||||
|
||||
UGCS_CombatSystemComponent* UGCS_CombatAbility::GetCombatSystemFromActorInfo() const
|
||||
{
|
||||
if (UGCS_CombatSystemComponent* CSS = UGCS_CombatSystemComponent::GetCombatSystemComponent(GetAvatarActorFromActorInfo()))
|
||||
{
|
||||
return CSS;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
UObject* UGCS_CombatAbility::GetCombatEntityFromActorInfo() const
|
||||
{
|
||||
return UGCS_CombatFunctionLibrary::GetCombatEntity(GetAvatarActorFromActorInfo());
|
||||
}
|
||||
|
||||
TScriptInterface<IGCS_CombatEntityInterface> UGCS_CombatAbility::GetCombatEntityInterfaceFromActorInfo() const
|
||||
{
|
||||
return UGCS_CombatFunctionLibrary::GetCombatEntityInterface(GetAvatarActorFromActorInfo());
|
||||
}
|
||||
@@ -0,0 +1,357 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
|
||||
#include "Abilities/GCS_ComboAbility.h"
|
||||
|
||||
#include "AbilitySystemComponent.h"
|
||||
#include "GCS_CombatEntityInterface.h"
|
||||
#include "GCS_CombatSystemComponent.h"
|
||||
#include "GCS_LogChannels.h"
|
||||
#include "GGA_GameplayTags.h"
|
||||
#include "Abilities/Tasks/AbilityTask_WaitInputPress.h"
|
||||
#include "Combo/GCS_ComboDefinition.h"
|
||||
#include "Utilities/GGA_AbilitySystemFunctionLibrary.h"
|
||||
#include "Weapon/GCS_WeaponInterface.h"
|
||||
|
||||
UGCS_ComboAbility::UGCS_ComboAbility()
|
||||
{
|
||||
ReplicationPolicy = EGameplayAbilityReplicationPolicy::ReplicateYes;
|
||||
NetExecutionPolicy = EGameplayAbilityNetExecutionPolicy::LocalPredicted;
|
||||
AbilityTags.AddTagFast(GGA_AbilityTraitTags::ActivationOnSpawn);
|
||||
}
|
||||
|
||||
void UGCS_ComboAbility::PreActivate(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo,
|
||||
FOnGameplayAbilityEnded::FDelegate* OnGameplayAbilityEndedDelegate, const FGameplayEventData* TriggerEventData)
|
||||
{
|
||||
Super::PreActivate(Handle, ActorInfo, ActivationInfo, OnGameplayAbilityEndedDelegate, TriggerEventData);
|
||||
}
|
||||
|
||||
void UGCS_ComboAbility::ActivateAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo,
|
||||
const FGameplayEventData* TriggerEventData)
|
||||
{
|
||||
UGCS_CombatSystemComponent* CombatSys = GetCombatSystemFromActorInfo();
|
||||
if (!CombatSys)
|
||||
{
|
||||
EndAbility(CurrentSpecHandle, CurrentActorInfo, CurrentActivationInfo, true, false);
|
||||
return;
|
||||
}
|
||||
|
||||
UAbilitySystemComponent* ASC = GetAbilitySystemComponentFromActorInfo();
|
||||
AbilityEndedDelegateHandle = ASC->OnAbilityEnded.AddUObject(this, &ThisClass::HandleAbilityEnd);
|
||||
|
||||
Super::ActivateAbility(Handle, ActorInfo, ActivationInfo, TriggerEventData);
|
||||
}
|
||||
|
||||
bool UGCS_ComboAbility::CanActivateAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayTagContainer* SourceTags,
|
||||
const FGameplayTagContainer* TargetTags, FGameplayTagContainer* OptionalRelevantTags) const
|
||||
{
|
||||
return Super::CanActivateAbility(Handle, ActorInfo, SourceTags, TargetTags, OptionalRelevantTags);
|
||||
}
|
||||
|
||||
void UGCS_ComboAbility::OnGiveAbility(const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilitySpec& Spec)
|
||||
{
|
||||
Super::OnGiveAbility(ActorInfo, Spec);
|
||||
// GiveSubAbilities(Spec);
|
||||
}
|
||||
|
||||
void UGCS_ComboAbility::OnRemoveAbility(const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilitySpec& Spec)
|
||||
{
|
||||
Super::OnRemoveAbility(ActorInfo, Spec);
|
||||
// RemoveSubAbilities();
|
||||
}
|
||||
|
||||
void UGCS_ComboAbility::EndAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, bool bReplicateEndAbility,
|
||||
bool bWasCancelled)
|
||||
{
|
||||
UAbilitySystemComponent* ASC = GetAbilitySystemComponentFromActorInfo();
|
||||
|
||||
if (IsValid(ASC) && AbilityEndedDelegateHandle.IsValid())
|
||||
{
|
||||
ASC->OnAbilityEnded.Remove(AbilityEndedDelegateHandle);
|
||||
AbilityEndedDelegateHandle.Reset();
|
||||
}
|
||||
|
||||
// final check to make current ability ends(no callback will fired.)!
|
||||
if (CurrentAbility.IsValid() && !bCurrentAbilityEnded)
|
||||
{
|
||||
ASC->CancelAbilityHandle(CurrentAbility);
|
||||
CurrentAbility = FGameplayAbilitySpecHandle();
|
||||
CurrentAbilityClass = nullptr;
|
||||
}
|
||||
|
||||
ResetCombo();
|
||||
|
||||
Super::EndAbility(Handle, ActorInfo, ActivationInfo, bReplicateEndAbility, bWasCancelled);
|
||||
}
|
||||
|
||||
bool UGCS_ComboAbility::AllowAdvanceCombo_Implementation() const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
void UGCS_ComboAbility::StartCombo_Implementation(const FGameplayEventData& ComboEvent)
|
||||
{
|
||||
HandleComboExecution(ComboEvent);
|
||||
}
|
||||
|
||||
void UGCS_ComboAbility::AdvanceCombo_Implementation(const FGameplayEventData& ComboEventData)
|
||||
{
|
||||
if (!AllowAdvanceCombo())
|
||||
{
|
||||
GCS_CLOG(Verbose, "Can't advanced combo due to AllowAdvanceCombo.")
|
||||
return;
|
||||
}
|
||||
|
||||
HandleComboExecution(ComboEventData);
|
||||
}
|
||||
|
||||
void UGCS_ComboAbility::ResetCombo_Implementation()
|
||||
{
|
||||
DesiredComboStep = INDEX_NONE;
|
||||
bCurrentAbilityEnded = false;
|
||||
UGCS_CombatSystemComponent* CombatSys = GetCombatSystemFromActorInfo();
|
||||
if (IsValid(CombatSys))
|
||||
{
|
||||
CombatSys->ResetComboState();
|
||||
}
|
||||
}
|
||||
|
||||
void UGCS_ComboAbility::HandleAbilityEnd(const FAbilityEndedData& AbilityEndedData)
|
||||
{
|
||||
//Already ends
|
||||
if (bCurrentAbilityEnded)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
//no ability running.
|
||||
if (!CurrentAbility.IsValid() || CurrentAbility != AbilityEndedData.AbilitySpecHandle)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// GCS_CLOG(Verbose, "Current ability:%s ended. bWasCancelled:%d", *CurrentAbilityClass->GetName(), AbilityEndedData.bWasCancelled)
|
||||
bCurrentAbilityEnded = true;
|
||||
CurrentAbility = FGameplayAbilitySpecHandle();
|
||||
CurrentAbilityClass = nullptr;
|
||||
|
||||
// No next combo.
|
||||
if (DesiredComboStep == INDEX_NONE)
|
||||
{
|
||||
ResetCombo();
|
||||
}
|
||||
}
|
||||
|
||||
bool UGCS_ComboAbility::SelectComboDefinition(const FGameplayEventData& ComboEventData, int32 CurrentStep, FGCS_ComboDefinition& OutDefinition)
|
||||
{
|
||||
UAbilitySystemComponent* ASC = GetAbilitySystemComponentFromActorInfo();
|
||||
UObject* Combat = GetCombatEntityFromActorInfo();
|
||||
if (!ASC || !Combat) return false;
|
||||
|
||||
TObjectPtr<const UDataTable> ComboDefinitionTable = IGCS_CombatEntityInterface::Execute_GetComboDefinitionTable(Combat);
|
||||
if (ComboDefinitionTable == nullptr)
|
||||
{
|
||||
GCS_CLOG(Error, "No combo definition table found from combat interface:%s, check your GetComboDefinitionTable implementation!", *GetNameSafe(Combat));
|
||||
return false;
|
||||
}
|
||||
|
||||
FGameplayTagContainer CurrentTags;
|
||||
ASC->GetOwnedGameplayTags(CurrentTags);
|
||||
|
||||
// Find best matching row in data table
|
||||
for (const auto& RowPair : ComboDefinitionTable->GetRowMap())
|
||||
{
|
||||
const FGCS_ComboDefinition* Row = reinterpret_cast<const FGCS_ComboDefinition*>(RowPair.Value);
|
||||
if (!Row || Row->AbilityClass.IsNull()) continue;
|
||||
|
||||
if (!Row->TagQuery.IsEmpty() && !Row->TagQuery.Matches(CurrentTags))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// skip if event tag doesn't match.
|
||||
if (ComboEventData.EventTag.IsValid() && Row->EventTag.IsValid() && ComboEventData.EventTag != Row->EventTag)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// skip if event instigatorTags
|
||||
if (!Row->EventInstigatorTagQuery.IsEmpty() && !Row->EventInstigatorTagQuery.Matches(ComboEventData.InstigatorTags))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (CurrentStep > 0 && Row->MinComboStep == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// skip if MinComboStep < CurrentStep(only for non resetting combo)
|
||||
if (!Row->bResetComboStep && Row->MinComboStep > 0 && Row->MinComboStep < CurrentStep) continue;
|
||||
|
||||
// skip if ability class is invalid.
|
||||
TSubclassOf<UGameplayAbility> TempAbilityClass = Row->AbilityClass.LoadSynchronous();
|
||||
if (TempAbilityClass == nullptr)
|
||||
{
|
||||
GCS_CLOG(Verbose, "invalid ability class found on row name:%s", *RowPair.Key.ToString());
|
||||
continue;
|
||||
}
|
||||
|
||||
FGameplayAbilitySpecHandle FoundAbility;
|
||||
if (!UGGA_AbilitySystemFunctionLibrary::FindAbilityFromClass(ASC, FoundAbility, TempAbilityClass, GetCurrentSourceObject()))
|
||||
{
|
||||
GCS_CLOG(Verbose, "skipped ability of class(%s) as it is not exists on ASC.", *TempAbilityClass->GetName());
|
||||
continue;
|
||||
}
|
||||
|
||||
if (Row->bRunActivationTest)
|
||||
{
|
||||
FGameplayTagContainer RelevantTags;
|
||||
if (!UGGA_AbilitySystemFunctionLibrary::CanActivateAbility(ASC, FoundAbility, RelevantTags))
|
||||
{
|
||||
if (Row->bAbortIfActivationTestFailed)
|
||||
{
|
||||
GCS_CLOG(Verbose, "Activation test failed for ability:%s, reason:%s. Combo will be aborted.", *TempAbilityClass->GetName(), *RelevantTags.ToString());
|
||||
return false;
|
||||
}
|
||||
{
|
||||
GCS_CLOG(Verbose, "skipped ability of class(%s) due to activation test failed, reason:%s", *TempAbilityClass->GetName(), *RelevantTags.ToString());
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// skip if custom rule doesn't met.
|
||||
if (!CanSelectedComboDefinition(ComboEventData, CurrentStep, OutDefinition))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
GCS_CLOG(Verbose, "selected ability of class(%s) as next combo(%d).", *TempAbilityClass->GetName(), CurrentStep);
|
||||
OutDefinition = *Row;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool UGCS_ComboAbility::CanSelectedComboDefinition_Implementation(const FGameplayEventData& ComboEvent, int32 CurrentStep, const FGCS_ComboDefinition& ComboDefinition) const
|
||||
{
|
||||
// Example code.
|
||||
// FYourCustomData Data = ComboDefinition.Extension.Get<FYourCustomData>();
|
||||
// UObject* Weapon = IGCS_CombatInterface::Execute_GCS_GetWeapon(GetCombatInterfaceFromActorInfo(), nullptr);
|
||||
// return IGCS_WeaponInterface::Execute_GetWeaponTags(Weapon).MatchesQuery(Data.WeaponTagRequirements);
|
||||
return true;
|
||||
}
|
||||
|
||||
void UGCS_ComboAbility::HandleComboExecution(const FGameplayEventData& ComboEventData)
|
||||
{
|
||||
if (!IsLocallyControlled())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
UAbilitySystemComponent* ASC = GetAbilitySystemComponentFromActorInfo();
|
||||
if (!ASC) return;
|
||||
|
||||
UGCS_CombatSystemComponent* CombatSys = GetCombatSystemFromActorInfo();
|
||||
int32 CurrentStep = CombatSys ? CombatSys->GetComboStep() : 0;
|
||||
|
||||
bool bStartingCombo = CurrentStep == 0;
|
||||
|
||||
FGCS_ComboDefinition ComboDefinition;
|
||||
|
||||
if (!SelectComboDefinition(ComboEventData, CurrentStep, ComboDefinition))
|
||||
{
|
||||
GCS_CLOG(Verbose, "No next combo definition with current combo step:%d", CurrentStep);
|
||||
DesiredComboStep = INDEX_NONE; // mark for no next action.
|
||||
return;
|
||||
}
|
||||
|
||||
TSubclassOf<UGameplayAbility> FoundAbilityClass = ComboDefinition.AbilityClass.Get();
|
||||
|
||||
FGameplayAbilitySpecHandle FoundAbility;
|
||||
UGGA_AbilitySystemFunctionLibrary::FindAbilityFromClass(ASC, FoundAbility, FoundAbilityClass, GetCurrentSourceObject());
|
||||
check(FoundAbility.IsValid())
|
||||
|
||||
bool bShouldResetComboStep = !bStartingCombo && ComboDefinition.MinComboStep > 0 && ComboDefinition.bResetComboStep;
|
||||
|
||||
// Mark the desired step.
|
||||
DesiredComboStep = bShouldResetComboStep ? 0 : CombatSys->GetComboStep() + 1;
|
||||
|
||||
// Cancel current ability if still running.
|
||||
if (CurrentAbility.IsValid() && !bCurrentAbilityEnded)
|
||||
{
|
||||
ASC->CancelAbilityHandle(CurrentAbility);
|
||||
CurrentAbility = FGameplayAbilitySpecHandle();
|
||||
CurrentAbilityClass = nullptr;
|
||||
}
|
||||
|
||||
bCurrentAbilityEnded = false;
|
||||
|
||||
//Combo Ability本身是预测激活的.
|
||||
if (ASC->TryActivateAbility(FoundAbility, true))
|
||||
{
|
||||
CurrentAbility = FoundAbility;
|
||||
CurrentAbilityClass = FoundAbilityClass;
|
||||
CombatSys->UpdateComboStep(DesiredComboStep);
|
||||
GCS_CLOG(Verbose, "combo advanced from step %d to %d with ability:%s", CurrentStep, DesiredComboStep, *FoundAbilityClass->GetName());
|
||||
DesiredComboStep = INDEX_NONE;
|
||||
}
|
||||
else
|
||||
{
|
||||
GCS_CLOG(Verbose, "combo reset as the new ability(%s) can't be activated when advancing from step %d to %d.", *FoundAbilityClass->GetName(), CurrentStep, DesiredComboStep);
|
||||
ResetCombo();
|
||||
}
|
||||
}
|
||||
|
||||
// void UGCS_ComboAbility::GiveSubAbilities(const FGameplayAbilitySpec& CurrentSpec)
|
||||
// {
|
||||
// UAbilitySystemComponent* ASC = GetAbilitySystemComponentFromActorInfo();
|
||||
// if (!IsValid(ASC) || !ASC->IsOwnerActorAuthoritative())
|
||||
// {
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// // Find best matching row in data table
|
||||
// for (const auto& RowPair : ComboDefinitionTable->GetRowMap())
|
||||
// {
|
||||
// const FGCS_ComboDefinition* Row = reinterpret_cast<const FGCS_ComboDefinition*>(RowPair.Value);
|
||||
// if (!Row || Row->AbilityClass.IsNull()) continue;
|
||||
// TSubclassOf<UGameplayAbility> AbilityClass = Row->AbilityClass.LoadSynchronous();
|
||||
//
|
||||
// if (FGameplayAbilitySpec* ExistingSpec = UGGA_AbilitySystemFunctionLibrary::FindAbilitySpecFromClass(ASC, AbilityClass, CurrentSpec.SourceObject.Get()))
|
||||
// {
|
||||
// GCS_CLOG(Warning, "Found existing ability with same class(%s) and source object(%s),skipped.", *AbilityClass->GetName(), *GetNameSafe(CurrentSpec.SourceObject.Get()))
|
||||
// continue;
|
||||
// }
|
||||
//
|
||||
// FGameplayAbilitySpec NewSpec = ASC->BuildAbilitySpecFromClass(AbilityClass, Row->MinComboStep > 0 ? Row->MinComboStep : 0);
|
||||
// NewSpec.SourceObject = CurrentSpec.SourceObject;
|
||||
// const FGameplayAbilitySpecHandle& AbilitySpecHandle = ASC->GiveAbility(NewSpec);
|
||||
// if (AbilitySpecHandle.IsValid())
|
||||
// {
|
||||
// AvailableAbilities.Add(AbilitySpecHandle);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// void UGCS_ComboAbility::RemoveSubAbilities()
|
||||
// {
|
||||
// UAbilitySystemComponent* ASC = GetAbilitySystemComponentFromActorInfo();
|
||||
// if (!IsValid(ASC))
|
||||
// {
|
||||
// return;
|
||||
// }
|
||||
// for (FGameplayAbilitySpecHandle SubAbilityHandle : AvailableAbilities)
|
||||
// {
|
||||
// ASC->ClearAbility(SubAbilityHandle);
|
||||
// }
|
||||
// AvailableAbilities.Empty();
|
||||
// }
|
||||
|
||||
#if WITH_EDITOR
|
||||
EDataValidationResult UGCS_ComboAbility::IsDataValid(class FDataValidationContext& Context) const
|
||||
{
|
||||
return Super::IsDataValid(Context);
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,255 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
#include "AbilitySystem/Attributes/AS_Poise.h"
|
||||
|
||||
#include "Net/UnrealNetwork.h"
|
||||
#include "AbilitySystemBlueprintLibrary.h"
|
||||
#include "GameplayEffectExtension.h"
|
||||
#include "GGA_GameplayAttributesHelper.h"
|
||||
#include "GGA_AttributeSystemComponent.h"
|
||||
|
||||
namespace AS_Poise
|
||||
{
|
||||
|
||||
UE_DEFINE_GAMEPLAY_TAG_COMMENT(Poise, TEXT("GGF.Attribute.PoiseSet.Poise"), "Current Poise value of an actor.(actor的当前抗打击值)")
|
||||
|
||||
UE_DEFINE_GAMEPLAY_TAG_COMMENT(MaxPoise, TEXT("GGF.Attribute.PoiseSet.MaxPoise"), "Max Poise value of an actor.(actor的最大抗打击值)")
|
||||
|
||||
UE_DEFINE_GAMEPLAY_TAG_COMMENT(PoiseRecover, TEXT("GGF.Attribute.PoiseSet.PoiseRecover"), "How many Poise to recover per second.(每秒恢复抗打击值)")
|
||||
|
||||
|
||||
}
|
||||
|
||||
UAS_Poise::UAS_Poise()
|
||||
{
|
||||
|
||||
UGGA_GameplayAttributesHelper::RegisterTagToAttribute(AS_Poise::Poise,GetPoiseAttribute());
|
||||
|
||||
UGGA_GameplayAttributesHelper::RegisterTagToAttribute(AS_Poise::MaxPoise,GetMaxPoiseAttribute());
|
||||
|
||||
UGGA_GameplayAttributesHelper::RegisterTagToAttribute(AS_Poise::PoiseRecover,GetPoiseRecoverAttribute());
|
||||
|
||||
|
||||
}
|
||||
|
||||
void UAS_Poise::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
|
||||
{
|
||||
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
|
||||
|
||||
DOREPLIFETIME_CONDITION_NOTIFY(ThisClass, Poise, COND_None, REPNOTIFY_Always);
|
||||
|
||||
DOREPLIFETIME_CONDITION_NOTIFY(ThisClass, MaxPoise, COND_None, REPNOTIFY_Always);
|
||||
|
||||
DOREPLIFETIME_CONDITION_NOTIFY(ThisClass, PoiseRecover, COND_None, REPNOTIFY_Always);
|
||||
|
||||
}
|
||||
|
||||
|
||||
void UAS_Poise::PreAttributeChange(const FGameplayAttribute& Attribute, float& NewValue)
|
||||
{
|
||||
Super::PreAttributeChange(Attribute, NewValue);
|
||||
|
||||
|
||||
if (Attribute == GetPoiseAttribute())
|
||||
{
|
||||
NewValue = FMath::Clamp(NewValue,0,GetMaxPoise());
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
if (AActor* Actor = GetOwningActor())
|
||||
{
|
||||
if (UGGA_AttributeSystemComponent* ASS = Actor->FindComponentByClass<UGGA_AttributeSystemComponent>())
|
||||
{
|
||||
ASS->ReceivePreAttributeChange(this,Attribute,NewValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool UAS_Poise::PreGameplayEffectExecute(FGameplayEffectModCallbackData& Data)
|
||||
{
|
||||
if (AActor* Actor = GetOwningActor())
|
||||
{
|
||||
if (UGGA_AttributeSystemComponent* ASS = Actor->FindComponentByClass<UGGA_AttributeSystemComponent>())
|
||||
{
|
||||
return ASS->ReceivePreGameplayEffectExecute(this, Data);
|
||||
}
|
||||
}
|
||||
|
||||
return Super::PreGameplayEffectExecute(Data);
|
||||
}
|
||||
|
||||
void UAS_Poise::PostAttributeChange(const FGameplayAttribute& Attribute, float OldValue, float NewValue)
|
||||
{
|
||||
Super::PostAttributeChange(Attribute, OldValue, NewValue);
|
||||
|
||||
|
||||
|
||||
if (Attribute == GetMaxPoiseAttribute())
|
||||
{
|
||||
AdjustAttributeForMaxChange(Poise, OldValue, NewValue, GetPoiseAttribute());
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
if (AActor* Actor = GetOwningActor())
|
||||
{
|
||||
if (UGGA_AttributeSystemComponent* ASS = Actor->FindComponentByClass<UGGA_AttributeSystemComponent>())
|
||||
{
|
||||
ASS->ReceivePostAttributeChange(this, Attribute, OldValue, NewValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UAS_Poise::PostGameplayEffectExecute(const FGameplayEffectModCallbackData& Data)
|
||||
{
|
||||
Super::PostGameplayEffectExecute(Data);
|
||||
|
||||
|
||||
if (Data.EvaluatedData.Attribute == GetPoiseAttribute())
|
||||
{
|
||||
SetPoise(FMath::Clamp(GetPoise(),0,GetMaxPoise()));
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
if (AActor* Actor = GetOwningActor())
|
||||
{
|
||||
if (UGGA_AttributeSystemComponent* ASS = Actor->FindComponentByClass<UGGA_AttributeSystemComponent>())
|
||||
{
|
||||
ASS->ReceivePostGameplayEffectExecute(this,Data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UAS_Poise::AdjustAttributeForMaxChange(FGameplayAttributeData& AffectedAttribute, const FGameplayAttributeData& MaxAttribute, float NewMaxValue,
|
||||
const FGameplayAttribute& AffectedAttributeProperty)
|
||||
{
|
||||
UAbilitySystemComponent* AbilityComp = GetOwningAbilitySystemComponent();
|
||||
const float CurrentMaxValue = MaxAttribute.GetCurrentValue();
|
||||
if (!FMath::IsNearlyEqual(CurrentMaxValue, NewMaxValue) && AbilityComp)
|
||||
{
|
||||
// Change current value to maintain the current Val / Max percent
|
||||
const float CurrentValue = AffectedAttribute.GetCurrentValue();
|
||||
float NewDelta = (CurrentMaxValue > 0.f) ? (CurrentValue * NewMaxValue / CurrentMaxValue) - CurrentValue : NewMaxValue;
|
||||
|
||||
AbilityComp->ApplyModToAttributeUnsafe(AffectedAttributeProperty, EGameplayModOp::Additive, NewDelta);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
FGameplayAttribute UAS_Poise::Bp_GetPoiseAttribute()
|
||||
{
|
||||
return ThisClass::GetPoiseAttribute();
|
||||
}
|
||||
|
||||
float UAS_Poise::Bp_GetPoise() const
|
||||
{
|
||||
return GetPoise();
|
||||
}
|
||||
|
||||
void UAS_Poise::Bp_SetPoise(float NewValue)
|
||||
{
|
||||
SetPoise(NewValue);
|
||||
}
|
||||
|
||||
void UAS_Poise::Bp_InitPoise(float NewValue)
|
||||
{
|
||||
InitPoise(NewValue);
|
||||
}
|
||||
|
||||
void UAS_Poise::OnRep_Poise(const FGameplayAttributeData& OldValue)
|
||||
{
|
||||
GAMEPLAYATTRIBUTE_REPNOTIFY(ThisClass, Poise, OldValue);
|
||||
if (AActor* Actor = GetOwningActor())
|
||||
{
|
||||
if (UGGA_AttributeSystemComponent* ASS = Actor->FindComponentByClass<UGGA_AttributeSystemComponent>())
|
||||
{
|
||||
ASS->ReceiveAttributeChange(this,GetPoiseAttribute(),GetPoise(),OldValue.GetCurrentValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
FGameplayAttribute UAS_Poise::Bp_GetMaxPoiseAttribute()
|
||||
{
|
||||
return ThisClass::GetMaxPoiseAttribute();
|
||||
}
|
||||
|
||||
float UAS_Poise::Bp_GetMaxPoise() const
|
||||
{
|
||||
return GetMaxPoise();
|
||||
}
|
||||
|
||||
void UAS_Poise::Bp_SetMaxPoise(float NewValue)
|
||||
{
|
||||
SetMaxPoise(NewValue);
|
||||
}
|
||||
|
||||
void UAS_Poise::Bp_InitMaxPoise(float NewValue)
|
||||
{
|
||||
InitMaxPoise(NewValue);
|
||||
}
|
||||
|
||||
void UAS_Poise::OnRep_MaxPoise(const FGameplayAttributeData& OldValue)
|
||||
{
|
||||
GAMEPLAYATTRIBUTE_REPNOTIFY(ThisClass, MaxPoise, OldValue);
|
||||
if (AActor* Actor = GetOwningActor())
|
||||
{
|
||||
if (UGGA_AttributeSystemComponent* ASS = Actor->FindComponentByClass<UGGA_AttributeSystemComponent>())
|
||||
{
|
||||
ASS->ReceiveAttributeChange(this,GetMaxPoiseAttribute(),GetMaxPoise(),OldValue.GetCurrentValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
FGameplayAttribute UAS_Poise::Bp_GetPoiseRecoverAttribute()
|
||||
{
|
||||
return ThisClass::GetPoiseRecoverAttribute();
|
||||
}
|
||||
|
||||
float UAS_Poise::Bp_GetPoiseRecover() const
|
||||
{
|
||||
return GetPoiseRecover();
|
||||
}
|
||||
|
||||
void UAS_Poise::Bp_SetPoiseRecover(float NewValue)
|
||||
{
|
||||
SetPoiseRecover(NewValue);
|
||||
}
|
||||
|
||||
void UAS_Poise::Bp_InitPoiseRecover(float NewValue)
|
||||
{
|
||||
InitPoiseRecover(NewValue);
|
||||
}
|
||||
|
||||
void UAS_Poise::OnRep_PoiseRecover(const FGameplayAttributeData& OldValue)
|
||||
{
|
||||
GAMEPLAYATTRIBUTE_REPNOTIFY(ThisClass, PoiseRecover, OldValue);
|
||||
if (AActor* Actor = GetOwningActor())
|
||||
{
|
||||
if (UGGA_AttributeSystemComponent* ASS = Actor->FindComponentByClass<UGGA_AttributeSystemComponent>())
|
||||
{
|
||||
ASS->ReceiveAttributeChange(this,GetPoiseRecoverAttribute(),GetPoiseRecover(),OldValue.GetCurrentValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
|
||||
#include "AbilitySystem/DEPRECATED_GCS_AbilitySystemGlobals.h"
|
||||
|
||||
FGameplayEffectContext* UDEPRECATED_GCS_AbilitySystemGlobals::AllocGameplayEffectContext() const
|
||||
{
|
||||
return Super::AllocGameplayEffectContext();
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
|
||||
#include "AbilitySystem/Effects/GCS_GEComponent_PredictivelyExecute.h"
|
||||
|
||||
#include "GameplayEffect.h"
|
||||
#include "GCS_LogChannels.h"
|
||||
#include "Utility/GCS_CombatFunctionLibrary.h"
|
||||
|
||||
void UGCS_GEComponent_PredictivelyExecute::OnGameplayEffectApplied(FActiveGameplayEffectsContainer& ActiveGEContainer, FGameplayEffectSpec& GESpec, FPredictionKey& PredictionKey) const
|
||||
{
|
||||
FGCS_ContextPayload_Combat* Payload = UGCS_CombatFunctionLibrary::EffectContextGetMutableCombatPayload(GESpec.GetEffectContext());
|
||||
|
||||
if (Payload)
|
||||
{
|
||||
Payload->PredictionKey = PredictionKey;
|
||||
}
|
||||
|
||||
if (GESpec.GetEffectContext().GetInstigator()->GetLocalRole() == ROLE_AutonomousProxy)
|
||||
{
|
||||
if (Payload)
|
||||
{
|
||||
Payload->bIsPredictingContext = true;
|
||||
}
|
||||
GCS_LOG(Verbose, "Ctx:%s %s predictively execute:%s", *GetClientServerContextString(GESpec.GetEffectContext().GetInstigator()), *GetNameSafe(GESpec.GetEffectContext().GetInstigator()),
|
||||
*GetNameSafe(GESpec.Def))
|
||||
ActiveGEContainer.PredictivelyExecuteEffectSpec(GESpec, PredictionKey, bPredictGameplayCues);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
|
||||
#include "AbilitySystem/GCS_GameplayEffectContext.h"
|
||||
|
||||
void FGCS_ContextPayload_Combat::SetTaggedValue(const FGameplayTag& Tag, float NewValue)
|
||||
{
|
||||
if (Tag.IsValid())
|
||||
{
|
||||
bool bFound = false;
|
||||
for (FGCS_TaggedValue& TaggedValue : TaggedValues)
|
||||
{
|
||||
if (TaggedValue.Attribute == Tag)
|
||||
{
|
||||
TaggedValue.Value = NewValue;
|
||||
bFound = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!bFound)
|
||||
{
|
||||
FGCS_TaggedValue Temp;
|
||||
Temp.Attribute = Tag;
|
||||
Temp.Value = NewValue;
|
||||
TaggedValues.Add(Temp);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
float FGCS_ContextPayload_Combat::GetTaggedValue(const FGameplayTag& Tag) const
|
||||
{
|
||||
if (Tag.IsValid())
|
||||
{
|
||||
for (const FGCS_TaggedValue& TaggedValue : TaggedValues)
|
||||
{
|
||||
if (TaggedValue.Attribute == Tag)
|
||||
{
|
||||
return TaggedValue.Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0.0f;
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
|
||||
#include "GenericCombatSystem/Public/AbilitySystem/Tasks/GCS_AbilityTask_CollisionTrace.h"
|
||||
#include "GCS_LogChannels.h"
|
||||
#include "Collision/GCS_TraceSystemComponent.h"
|
||||
#include "Collision/DEPRECATED_GCS_CollisionTraceInstance.h"
|
||||
#include "CombatFlow/GCS_AttackRequest.h"
|
||||
#include "Utility/GCS_CombatFunctionLibrary.h"
|
||||
|
||||
|
||||
UGCS_AbilityTask_CollisionTrace::UGCS_AbilityTask_CollisionTrace()
|
||||
{
|
||||
// make sure this task runs on simulated proxy.
|
||||
bSimulatedTask = true;
|
||||
}
|
||||
|
||||
UGCS_AbilityTask_CollisionTrace* UGCS_AbilityTask_CollisionTrace::HandleCollisionTraces(UGameplayAbility* OwningAbility, FName TaskInstanceName, bool bAdjustVisibilityBasedAnimTickOption)
|
||||
{
|
||||
UGCS_AbilityTask_CollisionTrace* MyTask = NewAbilityTask<UGCS_AbilityTask_CollisionTrace>(OwningAbility, TaskInstanceName);
|
||||
MyTask->bAdjustAnimTickOption = bAdjustVisibilityBasedAnimTickOption;
|
||||
return MyTask;
|
||||
}
|
||||
|
||||
void UGCS_AbilityTask_CollisionTrace::Activate()
|
||||
{
|
||||
Super::Activate();
|
||||
|
||||
if (bAdjustAnimTickOption && GetAvatarActor()->GetNetMode() == NM_DedicatedServer)
|
||||
{
|
||||
if (USkeletalMeshComponent* SkeletalMeshComponent = UGCS_CombatFunctionLibrary::GetMainCharacterMeshComponent(GetAvatarActor()))
|
||||
{
|
||||
if (SkeletalMeshComponent->VisibilityBasedAnimTickOption != EVisibilityBasedAnimTickOption::AlwaysTickPoseAndRefreshBones)
|
||||
{
|
||||
PrevAnimTickOption = SkeletalMeshComponent->VisibilityBasedAnimTickOption;
|
||||
SkeletalMeshComponent->VisibilityBasedAnimTickOption = EVisibilityBasedAnimTickOption::AlwaysTickPoseAndRefreshBones;
|
||||
bAdjustAnimTickOption = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (UGCS_TraceSystemComponent* TSC = UGCS_TraceSystemComponent::GetTraceSystemComponent(GetAvatarActor()))
|
||||
{
|
||||
TSC->OnTraceHitEvent.AddDynamic(this, &ThisClass::TraceHitCallback);
|
||||
}
|
||||
}
|
||||
|
||||
void UGCS_AbilityTask_CollisionTrace::OnDestroy(bool bInOwnerFinished)
|
||||
{
|
||||
if (bAdjustAnimTickOption && bAdjustedAnimTickOption)
|
||||
{
|
||||
if (USkeletalMeshComponent* SkeletalMeshComponent = UGCS_CombatFunctionLibrary::GetMainCharacterMeshComponent(GetAvatarActor()))
|
||||
{
|
||||
SkeletalMeshComponent->VisibilityBasedAnimTickOption = PrevAnimTickOption;
|
||||
}
|
||||
}
|
||||
if (UGCS_TraceSystemComponent* TSC = UGCS_TraceSystemComponent::GetTraceSystemComponent(GetAvatarActor()))
|
||||
{
|
||||
TSC->OnTraceHitEvent.RemoveDynamic(this, &ThisClass::TraceHitCallback);
|
||||
for (auto& MeleeRequest : MeleeRequests)
|
||||
{
|
||||
TSC->StopTraces(MeleeRequest.Value);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
MeleeRequests.Empty();
|
||||
|
||||
Super::OnDestroy(bInOwnerFinished);
|
||||
}
|
||||
|
||||
void UGCS_AbilityTask_CollisionTrace::TraceHitCallback(const FGCS_TraceHandle& TraceHandle, const FHitResult& HitResult)
|
||||
{
|
||||
if (ShouldBroadcastAbilityTaskDelegates() && !MeleeRequests.IsEmpty() && TraceHandle.IsValidHandle())
|
||||
{
|
||||
TObjectPtr<const UGCS_AttackRequest_Melee> Req = nullptr;
|
||||
bool bFound = false;
|
||||
for (auto& MeleeRequest : MeleeRequests)
|
||||
{
|
||||
if (!MeleeRequest.Value.Contains(TraceHandle))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
Req = MeleeRequest.Key;
|
||||
bFound = true;
|
||||
break;
|
||||
}
|
||||
if (bFound)
|
||||
{
|
||||
OnTargetsFound.Broadcast(Req, TraceHandle, HitResult);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UGCS_AbilityTask_CollisionTrace::AddMeleeRequest(const UGCS_AttackRequest_Melee* Request, UObject* SourceObject)
|
||||
{
|
||||
if (IsValid(Request) && !MeleeRequests.Contains(Request))
|
||||
{
|
||||
const FGameplayTagContainer& TracesToControl = Request->TracesToControl;
|
||||
if (UGCS_TraceSystemComponent* TSC = UGCS_TraceSystemComponent::GetTraceSystemComponent(GetAvatarActor()))
|
||||
{
|
||||
TArray<FGCS_TraceHandle> Handles = TSC->StartTraces(TracesToControl, SourceObject);
|
||||
if (Handles.IsEmpty())
|
||||
{
|
||||
GCS_LOG(Warning, "Ability:(%s), No any trace started by melee request(%s) with source object(%s)", *GetNameSafe(Ability.Get()), *Request->GetPathName(), *GetNameSafe(SourceObject));
|
||||
}
|
||||
MeleeRequests.Emplace(Request, Handles);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UGCS_AbilityTask_CollisionTrace::RemoveMeleeRequest(const UGCS_AttackRequest_Melee* Request)
|
||||
{
|
||||
if (IsValid(Request) && MeleeRequests.Contains(Request))
|
||||
{
|
||||
if (UGCS_TraceSystemComponent* TSC = UGCS_TraceSystemComponent::GetTraceSystemComponent(GetAvatarActor()))
|
||||
{
|
||||
TSC->StopTraces(MeleeRequests[Request]);
|
||||
}
|
||||
MeleeRequests.Remove(Request);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
|
||||
#include "Bullet/GCS_BulletContainer.h"
|
||||
|
||||
void FGCS_BulletContainer::PreReplicatedRemove(const TArrayView<int32> RemovedIndices, int32 FinalSize)
|
||||
{
|
||||
}
|
||||
|
||||
void FGCS_BulletContainer::PostReplicatedAdd(const TArrayView<int32> AddedIndices, int32 FinalSize)
|
||||
{
|
||||
}
|
||||
|
||||
void FGCS_BulletContainer::PostReplicatedChange(const TArrayView<int32> ChangedIndices, int32 FinalSize)
|
||||
{
|
||||
}
|
||||
|
||||
int32 FGCS_BulletContainer::IndexOfById(const FGuid& Id) const
|
||||
{
|
||||
if (!Id.IsValid())
|
||||
{
|
||||
return INDEX_NONE;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
@@ -0,0 +1,441 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
|
||||
#include "Bullet/GCS_BulletInstance.h"
|
||||
#include "AbilitySystemBlueprintLibrary.h"
|
||||
#include "AbilitySystemComponent.h"
|
||||
#include "GCS_GameplayTags.h"
|
||||
#include "GCS_LogChannels.h"
|
||||
#include "Engine/World.h"
|
||||
#include "GameFramework/Pawn.h"
|
||||
#include "Bullet/GCS_BulletStructLibrary.h"
|
||||
#include "Bullet/GCS_BulletSubsystem.h"
|
||||
#include "CombatFlow/GCS_AttackDefinition.h"
|
||||
#include "GameFramework/ProjectileMovementComponent.h"
|
||||
#include "NiagaraSystem.h"
|
||||
#include "Net/UnrealNetwork.h"
|
||||
#include "Utilities/GGA_GameplayEffectContainerFunctionLibrary.h"
|
||||
#include "Utilities/GGA_GameplayEffectFunctionLibrary.h"
|
||||
#include "Utility/GCS_CombatFunctionLibrary.h"
|
||||
|
||||
// Sets default values
|
||||
AGCS_BulletInstance::AGCS_BulletInstance()
|
||||
{
|
||||
// Set this actor to call Tick() every frame. You can turn this off to improve performance if you don't need it.
|
||||
PrimaryActorTick.bCanEverTick = true;
|
||||
PrimaryActorTick.bAllowTickBatching = true;
|
||||
|
||||
bReplicates = true;
|
||||
|
||||
ProjectileMovement = CreateDefaultSubobject<UProjectileMovementComponent>("ProjectileMovement");
|
||||
}
|
||||
|
||||
void AGCS_BulletInstance::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
|
||||
{
|
||||
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
|
||||
|
||||
FDoRepLifetimeParams SharedParams;
|
||||
SharedParams.bIsPushBased = true;
|
||||
|
||||
DOREPLIFETIME_WITH_PARAMS_FAST(ThisClass, DefinitionHandle, SharedParams);
|
||||
DOREPLIFETIME_WITH_PARAMS_FAST(ThisClass, BulletId, SharedParams);
|
||||
}
|
||||
|
||||
UProjectileMovementComponent* AGCS_BulletInstance::GetProjectileMovementComponent() const
|
||||
{
|
||||
return ProjectileMovement;
|
||||
}
|
||||
|
||||
void AGCS_BulletInstance::SetDefinitionHandle(FDataTableRowHandle NewHandle)
|
||||
{
|
||||
if ((GetOwner() != nullptr && GetOwner()->HasAuthority()) || HasAuthority())
|
||||
{
|
||||
MARK_PROPERTY_DIRTY_FROM_NAME(ThisClass, DefinitionHandle, this);
|
||||
DefinitionHandle = NewHandle;
|
||||
ForceNetUpdate();
|
||||
OnRep_BulletDefinition();
|
||||
}
|
||||
}
|
||||
|
||||
void AGCS_BulletInstance::SetBulletId(const FGuid& NewId)
|
||||
{
|
||||
if (NewId.IsValid())
|
||||
{
|
||||
if ((GetOwner() != nullptr && GetOwner()->HasAuthority()) || HasAuthority())
|
||||
{
|
||||
MARK_PROPERTY_DIRTY_FROM_NAME(ThisClass, DefinitionHandle, this);
|
||||
}
|
||||
BulletId = NewId;
|
||||
}
|
||||
else
|
||||
{
|
||||
UE_LOG(LogGCS, Error, TEXT("Attempt to set invalid guid for bullet(%s)"), *GetName())
|
||||
}
|
||||
}
|
||||
|
||||
FGuid AGCS_BulletInstance::GetBulletId() const
|
||||
{
|
||||
return BulletId;
|
||||
}
|
||||
|
||||
void AGCS_BulletInstance::SetParentBulletId_Implementation(FGuid NewParentId)
|
||||
{
|
||||
ParentBulletId = NewParentId;
|
||||
}
|
||||
|
||||
FGuid AGCS_BulletInstance::GetParentBulletId() const
|
||||
{
|
||||
return ParentBulletId;
|
||||
}
|
||||
|
||||
void AGCS_BulletInstance::SetHitResult(const FHitResult& NewHitResult)
|
||||
{
|
||||
LastHitResult = NewHitResult;
|
||||
// HitActors.Push(NewHitResult.GetActor());
|
||||
}
|
||||
|
||||
const FHitResult& AGCS_BulletInstance::GetHitResult() const
|
||||
{
|
||||
return LastHitResult;
|
||||
}
|
||||
|
||||
bool AGCS_BulletInstance::HasGameplayAuthority() const
|
||||
{
|
||||
return HasAuthority() && !bIsLocalPredicting;
|
||||
}
|
||||
|
||||
void AGCS_BulletInstance::LaunchBullet_Implementation()
|
||||
{
|
||||
}
|
||||
|
||||
bool AGCS_BulletInstance::GetEffectSpecHandle_Implementation(FGameplayEffectSpecHandle& OutHandle)
|
||||
{
|
||||
OutHandle = EffectSpecHandle;
|
||||
return EffectSpecHandle.IsValid();
|
||||
}
|
||||
|
||||
FGGA_GameplayEffectContainer AGCS_BulletInstance::GetEffectContainer_Implementation() const
|
||||
{
|
||||
if (FGCS_AttackDefinition* AtkDef = Definition.AttackDefinition.GetRow<FGCS_AttackDefinition>(TEXT("AGCS_BulletInstance::GetEffectContainer")))
|
||||
{
|
||||
return AtkDef->TargetEffectContainer;
|
||||
}
|
||||
return FGGA_GameplayEffectContainer();
|
||||
}
|
||||
|
||||
int32 AGCS_BulletInstance::GetEffectContainerLevelOverride_Implementation() const
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
void AGCS_BulletInstance::SetEffectContainerSpec_Implementation(const FGGA_GameplayEffectContainerSpec& InEffectContainerSpec)
|
||||
{
|
||||
EffectContainerSpec = InEffectContainerSpec;
|
||||
}
|
||||
|
||||
void AGCS_BulletInstance::SetEffectSpec_Implementation(FGameplayEffectSpecHandle& InEffectSpec)
|
||||
{
|
||||
EffectSpecHandle = InEffectSpec;
|
||||
}
|
||||
|
||||
UShapeComponent* AGCS_BulletInstance::GetBulletShape_Implementation() const
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
FGGA_GameplayEffectContainerSpec AGCS_BulletInstance::GetEffectContainerSpec_Implementation() const
|
||||
{
|
||||
return EffectContainerSpec;
|
||||
}
|
||||
|
||||
void AGCS_BulletInstance::PostNetInit()
|
||||
{
|
||||
Super::PostNetInit();
|
||||
}
|
||||
|
||||
void AGCS_BulletInstance::PostNetReceive()
|
||||
{
|
||||
Super::PostNetReceive();
|
||||
}
|
||||
|
||||
void AGCS_BulletInstance::FoundLocalPredictedBullet_Implementation(AGCS_BulletInstance* PredictedBullet)
|
||||
{
|
||||
}
|
||||
|
||||
TSubclassOf<UGameplayEffect> AGCS_BulletInstance::GetEffectClass_Implementation() const
|
||||
{
|
||||
if (FGCS_AttackDefinition* AtkDef = Definition.AttackDefinition.GetRow<FGCS_AttackDefinition>(TEXT("AGCS_BulletInstance::GetEffectClass")))
|
||||
{
|
||||
if (!AtkDef->TargetEffectClass.IsNull())
|
||||
{
|
||||
return AtkDef->TargetEffectClass.LoadSynchronous();
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
int32 AGCS_BulletInstance::GetEffectLevel_Implementation() const
|
||||
{
|
||||
if (FGCS_AttackDefinition* AtkDef = Definition.AttackDefinition.GetRow<FGCS_AttackDefinition>(TEXT("AGCS_BulletInstance::GetEffectClass")))
|
||||
{
|
||||
return AtkDef->TargetEffectClassLevel;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
// Called when the game starts or when spawned
|
||||
void AGCS_BulletInstance::BeginPlay()
|
||||
{
|
||||
Super::BeginPlay();
|
||||
}
|
||||
|
||||
void AGCS_BulletInstance::EndPlay(const EEndPlayReason::Type EndPlayReason)
|
||||
{
|
||||
OnBulletEndPlay();
|
||||
Super::EndPlay(EndPlayReason);
|
||||
}
|
||||
|
||||
void AGCS_BulletInstance::OnBulletBeginPlay_Implementation()
|
||||
{
|
||||
SetActorHiddenInGame(false);
|
||||
SetActorTickEnabled(true);
|
||||
SetActorEnableCollision(true);
|
||||
}
|
||||
|
||||
void AGCS_BulletInstance::OnBulletEndPlay_Implementation()
|
||||
{
|
||||
SetActorHiddenInGame(true);
|
||||
SetActorTickEnabled(false);
|
||||
SetActorEnableCollision(false);
|
||||
bIsLocalPredicting = false;
|
||||
Definition = FGCS_BulletDefinition();
|
||||
Request = nullptr;
|
||||
EffectSpecHandle = FGameplayEffectSpecHandle();
|
||||
EffectContainerSpec = FGGA_GameplayEffectContainerSpec();
|
||||
SetActorTransform(FTransform::Identity);
|
||||
}
|
||||
|
||||
void AGCS_BulletInstance::SetupInitialLocationAndRotation()
|
||||
{
|
||||
InitialActorLocation = GetActorLocation();
|
||||
InitialActorRotation = GetActorRotation();
|
||||
}
|
||||
|
||||
void AGCS_BulletInstance::RefreshTravelStates()
|
||||
{
|
||||
if (HasAuthority() && GetProjectileMovementComponent() && GetProjectileMovementComponent()->IsActive())
|
||||
{
|
||||
// update Traveled distance and gravity scale.
|
||||
TraveledDistance = FVector::Dist2D(GetActorLocation(), InitialActorLocation);
|
||||
float DesiredGravityScale = TraveledDistance <= Definition.AttenuationRange ? Definition.GravityScaleInRange : Definition.GravityScaleOutRage;
|
||||
if (GetProjectileMovementComponent()->ProjectileGravityScale != DesiredGravityScale)
|
||||
{
|
||||
GetProjectileMovementComponent()->ProjectileGravityScale = DesiredGravityScale;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// bool AGCS_BulletInstance::AlreadyHit(const FHitResult& InHitResult) const
|
||||
// {
|
||||
// for (int i = 0; i < HitActors.Num(); ++i)
|
||||
// {
|
||||
// if (HitActors[i] == InHitResult.GetActor())
|
||||
// {
|
||||
// return true;
|
||||
// }
|
||||
// }
|
||||
// return false;
|
||||
// }
|
||||
|
||||
bool AGCS_BulletInstance::ShouldPenetrateHitResult(const FHitResult& InHitResult) const
|
||||
{
|
||||
if (InHitResult.GetActor() != nullptr)
|
||||
{
|
||||
if (Definition.bPenetrateCharacter && InHitResult.GetActor()->GetClass()->IsChildOf(APawn::StaticClass()))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return Definition.bPenetrateMap;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AGCS_BulletInstance::ShouldGenerateBullet_Implementation()
|
||||
{
|
||||
if (Definition.HitBulletDefinition.IsNull() || Definition.HitBulletDefinition == DefinitionHandle)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (Definition.LaunchCondition == GCS_BulletLaunch::Always || Definition.LaunchCondition == FGameplayTag::EmptyTag)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (Definition.LaunchCondition == GCS_BulletLaunch::DidNotHitPawn)
|
||||
{
|
||||
if (LastHitResult.GetActor() && !LastHitResult.GetActor()->GetClass()->IsChildOf(APawn::StaticClass()))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (Definition.LaunchCondition == GCS_BulletLaunch::HitPawn)
|
||||
{
|
||||
if (LastHitResult.GetActor() && LastHitResult.GetActor()->GetClass()->IsChildOf(APawn::StaticClass()))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
void AGCS_BulletInstance::HandleBulletHitChains_Implementation()
|
||||
{
|
||||
if (!HasGameplayAuthority() || !ShouldGenerateBullet())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
UAbilitySystemComponent* AbilitySystem = UAbilitySystemBlueprintLibrary::GetAbilitySystemComponent(GetOwner());
|
||||
if (AbilitySystem == nullptr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
FGCS_BulletDefinition* SubBullet = Definition.HitBulletDefinition.GetRow<FGCS_BulletDefinition>(TEXT("HandleBulletHitChains"));
|
||||
if (SubBullet == nullptr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// Setup bullet gameplay effect instance and launch.
|
||||
|
||||
FGCS_BulletSpawnParameters SpawnParams;
|
||||
SpawnParams.Owner = GetOwner();
|
||||
SpawnParams.DefinitionHandle = Definition.HitBulletDefinition;
|
||||
|
||||
//TODO Various different launch location.
|
||||
FTransform SpawnTransform = FTransform::Identity;
|
||||
SpawnTransform.SetLocation(GetHitResult().Location);
|
||||
SpawnTransform.SetRotation(GetActorRotation().Quaternion());
|
||||
SpawnTransform.SetScale3D(FVector::One());
|
||||
SpawnParams.SpawnTransform = SpawnTransform;
|
||||
SpawnParams.Request = Request;
|
||||
SpawnParams.ParentId = BulletId;
|
||||
|
||||
FGameplayEventData EventData;
|
||||
EventData.Instigator = GetOwner();
|
||||
EventData.EventMagnitude = GetEffectContainerLevelOverride_Implementation();
|
||||
|
||||
TArray<AGCS_BulletInstance*> BulletInstances = GetWorld()->GetSubsystem<UGCS_BulletSubsystem>()->SpawnBullets(SpawnParams);
|
||||
|
||||
//Setup each bullets
|
||||
for (AGCS_BulletInstance* BulletInstance : BulletInstances)
|
||||
{
|
||||
// Setup normal gameplay effects.
|
||||
TSubclassOf<UGameplayEffect> GE = Execute_GetEffectClass(BulletInstance);
|
||||
int32 GELevel = Execute_GetEffectLevel(BulletInstance);
|
||||
if (GE != nullptr)
|
||||
{
|
||||
FGameplayEffectSpecHandle GESpec = AbilitySystem->MakeOutgoingSpec(GE, GELevel, AbilitySystem->MakeEffectContext());
|
||||
UGCS_CombatFunctionLibrary::AddAttackHandleToGameplayEffectSpec(GESpec, SubBullet->AttackDefinition);
|
||||
FGameplayEffectContextHandle ContextHandle = UAbilitySystemBlueprintLibrary::GetEffectContext(GESpec);
|
||||
UGGA_GameplayEffectFunctionLibrary::SetEffectCauser(ContextHandle, BulletInstance);
|
||||
Execute_SetEffectSpec(BulletInstance, GESpec);
|
||||
}
|
||||
|
||||
// Setup gameplay effects container.
|
||||
const FGGA_GameplayEffectContainer& GEContainer = Execute_GetEffectContainer(BulletInstance);
|
||||
|
||||
if (UGGA_GameplayEffectContainerFunctionLibrary::IsValidContainer(GEContainer))
|
||||
{
|
||||
FGGA_GameplayEffectContainerSpec GEContainerSpec = UGGA_GameplayEffectContainerFunctionLibrary::MakeEffectContainerSpec(
|
||||
GEContainer, EventData);
|
||||
|
||||
// Setup each gameplay effect instance.
|
||||
for (const FGameplayEffectSpecHandle& GESpec : GEContainerSpec.TargetGameplayEffectSpecs)
|
||||
{
|
||||
UGCS_CombatFunctionLibrary::AddAttackHandleToGameplayEffectSpec(GESpec, SubBullet->AttackDefinition);
|
||||
FGameplayEffectContextHandle ContextHandle = UAbilitySystemBlueprintLibrary::GetEffectContext(GESpec);
|
||||
UGGA_GameplayEffectFunctionLibrary::SetEffectCauser(ContextHandle, BulletInstance);
|
||||
}
|
||||
Execute_SetEffectContainerSpec(BulletInstance, GEContainerSpec);
|
||||
}
|
||||
}
|
||||
|
||||
//Launch each bullets
|
||||
for (AGCS_BulletInstance* BulletInstance : BulletInstances)
|
||||
{
|
||||
BulletInstance->LaunchBullet();
|
||||
}
|
||||
}
|
||||
|
||||
void AGCS_BulletInstance::ApplyGameplayEffects_Implementation(FHitResult HitResult)
|
||||
{
|
||||
if (!HasGameplayAuthority())
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (UAbilitySystemComponent* TargetAsc = UAbilitySystemBlueprintLibrary::GetAbilitySystemComponent(HitResult.GetActor()))
|
||||
{
|
||||
FGameplayEffectSpecHandle SpecHandle;
|
||||
if (Execute_GetEffectSpecHandle(this, SpecHandle))
|
||||
{
|
||||
FGameplayEffectContextHandle ContextHandle = UAbilitySystemBlueprintLibrary::GetEffectContext(SpecHandle);
|
||||
ContextHandle.AddHitResult(HitResult, true);
|
||||
ContextHandle.GetInstigatorAbilitySystemComponent()->BP_ApplyGameplayEffectSpecToTarget(SpecHandle, TargetAsc);
|
||||
}
|
||||
|
||||
FGGA_GameplayEffectContainerSpec ContainerSpec = Execute_GetEffectContainerSpec(this);
|
||||
if (ContainerSpec.HasValidEffects())
|
||||
{
|
||||
FGameplayAbilityTargetData_SingleTargetHit* NewData = new FGameplayAbilityTargetData_SingleTargetHit(HitResult);
|
||||
ContainerSpec.TargetData.Add(NewData);
|
||||
for (const FGameplayEffectSpecHandle& TargetGameplayEffectSpec : ContainerSpec.TargetGameplayEffectSpecs)
|
||||
{
|
||||
TargetGameplayEffectSpec.Data->GetContext().AddHitResult(HitResult, true);
|
||||
}
|
||||
}
|
||||
UGGA_GameplayEffectContainerFunctionLibrary::ApplyExternalEffectContainerSpec(ContainerSpec);
|
||||
}
|
||||
}
|
||||
|
||||
void AGCS_BulletInstance::OnRep_BulletId(FGuid Prev)
|
||||
{
|
||||
if (UGCS_BulletSubsystem* BulletSubsystem = GetWorld()->GetSubsystem<UGCS_BulletSubsystem>())
|
||||
{
|
||||
if (!bIsLocalPredicting && BulletSubsystem->BulletInstances.Contains(BulletId))
|
||||
{
|
||||
UE_LOG(LogGCS, Warning, TEXT("Found local predicted bullet(%s)"), *BulletSubsystem->BulletInstances[BulletId]->GetName());
|
||||
FoundLocalPredictedBullet(BulletSubsystem->BulletInstances[BulletId]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AGCS_BulletInstance::OnRep_BulletDefinition()
|
||||
{
|
||||
if (DefinitionHandle.IsNull())
|
||||
{
|
||||
Definition = FGCS_BulletDefinition();
|
||||
OnBulletEndPlay();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (const FGCS_BulletDefinition* NewDefinition = DefinitionHandle.GetRow<FGCS_BulletDefinition>(TEXT("RefreshDefinition")))
|
||||
{
|
||||
Definition = *NewDefinition;
|
||||
OnBulletBeginPlay();
|
||||
}
|
||||
else
|
||||
{
|
||||
UE_LOG(LogGCS, Verbose, TEXT("Failed to load definition(%s) for bullet(%s)"), *DefinitionHandle.ToDebugString(), *GetPathName(this));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Called every frame
|
||||
void AGCS_BulletInstance::Tick(float DeltaTime)
|
||||
{
|
||||
Super::Tick(DeltaTime);
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
|
||||
#include "Bullet/GCS_BulletStructLibrary.h"
|
||||
#include "CombatFlow/GCS_AttackRequest.h"
|
||||
|
||||
FString FGCS_BulletSpawnParameters::ToDebugString() const
|
||||
{
|
||||
return FString::Format(TEXT("Owner:{0} DefinitionHandle:{1}"), {Owner ? Owner->GetName() : "Null", DefinitionHandle.ToDebugString()});
|
||||
}
|
||||
@@ -0,0 +1,197 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
|
||||
#include "Bullet/GCS_BulletSubsystem.h"
|
||||
|
||||
#include "GCS_LogChannels.h"
|
||||
#include "Bullet/GCS_BulletInstance.h"
|
||||
#include "Engine/World.h"
|
||||
#include "Kismet/KismetMathLibrary.h"
|
||||
|
||||
UGCS_BulletSubsystem* UGCS_BulletSubsystem::Get(const UWorld* World)
|
||||
{
|
||||
if (IsValid(World))
|
||||
{
|
||||
return World->GetSubsystem<UGCS_BulletSubsystem>();
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
TArray<AGCS_BulletInstance*> UGCS_BulletSubsystem::SpawnBullets(const FGCS_BulletSpawnParameters& SpawnParameters)
|
||||
{
|
||||
TArray<AGCS_BulletInstance*> RetInstances{};
|
||||
|
||||
FGCS_BulletDefinition Definition;
|
||||
if (SpawnParameters.DefinitionHandle.IsNull() || !LoadBulletDefinition(SpawnParameters.DefinitionHandle, Definition))
|
||||
{
|
||||
return RetInstances;
|
||||
}
|
||||
|
||||
RetInstances = GetOrCreateBulletInstances(SpawnParameters, Definition);
|
||||
|
||||
for (int i = 0; i < RetInstances.Num(); ++i)
|
||||
{
|
||||
AGCS_BulletInstance* Instance = RetInstances[i];
|
||||
|
||||
Instance->bServerInitiated = GetWorld()->GetNetMode() < NM_Client;
|
||||
Instance->bIsLocalPredicting = SpawnParameters.bIsLocalPredicting;
|
||||
|
||||
if (SpawnParameters.OverrideBulletIds.IsValidIndex(i))
|
||||
{
|
||||
Instance->SetBulletId(SpawnParameters.OverrideBulletIds[i]);
|
||||
}
|
||||
else
|
||||
{
|
||||
Instance->SetBulletId(FGuid::NewGuid());
|
||||
}
|
||||
|
||||
if (SpawnParameters.ParentId.IsValid() && BulletInstances.Contains(SpawnParameters.ParentId))
|
||||
{
|
||||
Instance->SetParentBulletId(SpawnParameters.ParentId);
|
||||
// if (AGCS_BulletInstance* ParentBulletInstance = BulletInstances[SpawnParameters.ParentId])
|
||||
// {
|
||||
// if (ParentBulletInstance->Definition.bUseSharedHitList)
|
||||
// {
|
||||
// Instance->HitActors = ParentBulletInstance->HitActors;
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
if (SpawnParameters.Request)
|
||||
{
|
||||
Instance->Request = SpawnParameters.Request;
|
||||
}
|
||||
|
||||
if (SpawnParameters.Owner)
|
||||
{
|
||||
Instance->SetOwner(SpawnParameters.Owner);
|
||||
}
|
||||
Instance->SetDefinitionHandle(SpawnParameters.DefinitionHandle);
|
||||
BulletInstances.Emplace(Instance->BulletId, Instance);
|
||||
}
|
||||
for (int i = 0; i < RetInstances.Num(); ++i)
|
||||
{
|
||||
FRotator RotationAdjustment(Definition.LaunchElevationAngle, Definition.LaunchAngle + Definition.LaunchAngleInterval * i, 0);
|
||||
|
||||
// Start with the spawn transform
|
||||
// FTransform ModifiedTransform = SpawnParameters.SpawnTransform;
|
||||
|
||||
// Compose the rotations: apply adjustment relative to original rotation
|
||||
// FRotator FinalRotation = UKismetMathLibrary::ComposeRotators(ModifiedTransform.Rotator(), RotationAdjustment);
|
||||
// ModifiedTransform.SetRotation(FinalRotation.Quaternion());
|
||||
|
||||
RetInstances[i]->SetActorTransform(FTransform(RotationAdjustment, FVector::ZeroVector) * SpawnParameters.SpawnTransform, false, nullptr, ETeleportType::ResetPhysics);
|
||||
}
|
||||
|
||||
//batch beginplay.
|
||||
for (AGCS_BulletInstance* Instance : RetInstances)
|
||||
{
|
||||
Instance->OnBulletBeginPlay();
|
||||
}
|
||||
return RetInstances;
|
||||
}
|
||||
|
||||
TArray<FGuid> UGCS_BulletSubsystem::GetIdsFromBullets(TArray<AGCS_BulletInstance*> Instances)
|
||||
{
|
||||
TArray<FGuid> Ids;
|
||||
for (AGCS_BulletInstance* BulletInstance : Instances)
|
||||
{
|
||||
Ids.Add(BulletInstance->BulletId);
|
||||
}
|
||||
return Ids;
|
||||
}
|
||||
|
||||
TArray<AGCS_BulletInstance*> UGCS_BulletSubsystem::GetOrCreateBulletInstances(const FGCS_BulletSpawnParameters& SpawnParameters, const FGCS_BulletDefinition& Definition)
|
||||
{
|
||||
TArray<AGCS_BulletInstance*> OutInstances;
|
||||
|
||||
static int32 MaxAllowedLoops = 30;
|
||||
|
||||
int32 Counter = 0;
|
||||
while (OutInstances.Num() < Definition.BulletCount)
|
||||
{
|
||||
if (AGCS_BulletInstance* Instance = TakeBulletFromPool(Definition.BulletActorClass.LoadSynchronous()))
|
||||
{
|
||||
OutInstances.Add(Instance);
|
||||
}
|
||||
else if (AGCS_BulletInstance* Instance2 = CreateBulletInstance(SpawnParameters, Definition))
|
||||
{
|
||||
OutInstances.Add(Instance2);
|
||||
}
|
||||
Counter++;
|
||||
if (Counter >= MaxAllowedLoops)
|
||||
{
|
||||
UE_LOG(LogGCS, Warning, TEXT("BulletSubsystem reach max allowed bullet spawn loops(%d)."), MaxAllowedLoops);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return OutInstances;
|
||||
}
|
||||
|
||||
AGCS_BulletInstance* UGCS_BulletSubsystem::TakeBulletFromPool(TSubclassOf<AGCS_BulletInstance> BulletClass)
|
||||
{
|
||||
int32 Found = INDEX_NONE;
|
||||
for (int i = 0; i < BulletPools.Num(); i++)
|
||||
{
|
||||
if (BulletPools[i].GetClass() == BulletClass)
|
||||
{
|
||||
Found = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (Found != INDEX_NONE)
|
||||
{
|
||||
AGCS_BulletInstance* FoundInstance = BulletPools[Found];
|
||||
UE_LOG(LogGCS, Verbose, TEXT("Taking bullet(%s) from pool."), *BulletClass->GetName());
|
||||
BulletPools.RemoveAtSwap(Found);
|
||||
return FoundInstance;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void UGCS_BulletSubsystem::DestroyBullet(FGuid BulletId)
|
||||
{
|
||||
if (BulletInstances.Contains(BulletId))
|
||||
{
|
||||
AGCS_BulletInstance* BulletToRemove = BulletInstances[BulletId];
|
||||
BulletToRemove->SetDefinitionHandle(FDataTableRowHandle());
|
||||
BulletToRemove->SetOwner(nullptr);
|
||||
BulletInstances.Remove(BulletId);
|
||||
BulletPools.Add(BulletToRemove);
|
||||
UE_LOG(LogGCS, Verbose, TEXT("Return bullet(%s) back to pool."), *BulletToRemove->GetClass()->GetName());
|
||||
}
|
||||
}
|
||||
|
||||
AGCS_BulletInstance* UGCS_BulletSubsystem::CreateBulletInstance(const FGCS_BulletSpawnParameters& SpawnParameters, const FGCS_BulletDefinition& Definition)
|
||||
{
|
||||
if (Definition.BulletActorClass.IsNull())
|
||||
{
|
||||
UE_LOG(LogGCS, Error, TEXT("Failed to create bullet instance for definition(%s),missing BulletActorClass!!!"), *SpawnParameters.DefinitionHandle.ToDebugString());
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
UClass* BulletClass = Definition.BulletActorClass.LoadSynchronous();
|
||||
check(BulletClass);
|
||||
FActorSpawnParameters ActorSpawnParameters;
|
||||
ActorSpawnParameters.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn;
|
||||
|
||||
AGCS_BulletInstance* NewInstance = GetWorld()->SpawnActor<AGCS_BulletInstance>(BulletClass, FTransform::Identity, ActorSpawnParameters);
|
||||
if (NewInstance)
|
||||
{
|
||||
UE_LOG(LogGCS, Verbose, TEXT("Create new bullet instance for class(%s)"), *BulletClass->GetName());
|
||||
return NewInstance;
|
||||
}
|
||||
|
||||
UE_LOG(LogGCS, Error, TEXT("Failed to create new bullet instance for class(%s)"), *BulletClass->GetName());
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool UGCS_BulletSubsystem::LoadBulletDefinition(const FDataTableRowHandle& Handle, FGCS_BulletDefinition& OutDefinition)
|
||||
{
|
||||
if (FGCS_BulletDefinition* Definition = Handle.GetRow<FGCS_BulletDefinition>(TEXT("LoadBulletDefinition")))
|
||||
{
|
||||
OutDefinition = *Definition;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
|
||||
#include "Bullet/GCS_BulletSystemComponent.h"
|
||||
|
||||
#include "GCS_LogChannels.h"
|
||||
#include "Bullet/GCS_BulletInstance.h"
|
||||
#include "Bullet/GCS_BulletSubsystem.h"
|
||||
|
||||
|
||||
// Sets default values for this component's properties
|
||||
UGCS_BulletSystemComponent::UGCS_BulletSystemComponent()
|
||||
{
|
||||
// Set this component to be initialized when the game starts, and to be ticked every frame. You can turn these features
|
||||
// off to improve performance if you don't need them.
|
||||
PrimaryComponentTick.bCanEverTick = false;
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
|
||||
// Called when the game starts
|
||||
void UGCS_BulletSystemComponent::BeginPlay()
|
||||
{
|
||||
Super::BeginPlay();
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
UGCS_BulletSystemComponent* UGCS_BulletSystemComponent::GetBulletSystemComponent(const AActor* Actor)
|
||||
{
|
||||
return IsValid(Actor) ? Actor->FindComponentByClass<UGCS_BulletSystemComponent>() : nullptr;
|
||||
}
|
||||
|
||||
bool UGCS_BulletSystemComponent::FindBulletSystemComponent(const AActor* Actor, UGCS_BulletSystemComponent*& Component)
|
||||
{
|
||||
Component = GetBulletSystemComponent(Actor);
|
||||
return Component != nullptr;
|
||||
}
|
||||
|
||||
void UGCS_BulletSystemComponent::SpawnBullet(const FGCS_BulletSpawnParameters& SpawnParameters)
|
||||
{
|
||||
//SpawnBulletInternal(SpawnParameters);
|
||||
}
|
||||
|
||||
// TArray<AGCS_BulletInstance*> UGCS_BulletSystemComponent::SpawnBulletInternal(const FGCS_BulletSpawnParameters& SpawnParameters)
|
||||
// {
|
||||
// TArray<AGCS_BulletInstance*> RetInstances{};
|
||||
//
|
||||
// if (GetOwner()->GetLocalRole() < ROLE_AutonomousProxy)
|
||||
// {
|
||||
// GCS_CLOG(Warning, "No authority to spawn bullet!")
|
||||
// return RetInstances;
|
||||
// }
|
||||
//
|
||||
// UGCS_BulletSubsystem* Subsystem = UGCS_BulletSubsystem::Get(GetWorld());
|
||||
// if (Subsystem == nullptr)
|
||||
// {
|
||||
// return RetInstances;
|
||||
// }
|
||||
//
|
||||
// FGCS_BulletDefinition Definition;
|
||||
// if (SpawnParameters.DefinitionHandle.IsNull() || !Subsystem->LoadBulletDefinition(SpawnParameters.DefinitionHandle, Definition))
|
||||
// {
|
||||
// return RetInstances;
|
||||
// }
|
||||
//
|
||||
// RetInstances = Subsystem->GetOrCreateBulletInstances(SpawnParameters, Definition);
|
||||
//
|
||||
// for (int i = 0; i < RetInstances.Num(); ++i)
|
||||
// {
|
||||
// AGCS_BulletInstance* Instance = RetInstances[i];
|
||||
//
|
||||
// Instance->bServerInitiated = GetWorld()->GetNetMode() < NM_Client;
|
||||
// Instance->bIsLocalPredicting = SpawnParameters.bIsLocalPredicting;
|
||||
//
|
||||
// if (SpawnParameters.OverrideBulletIds.IsValidIndex(i))
|
||||
// {
|
||||
// Instance->SetBulletId(SpawnParameters.OverrideBulletIds[0]);
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// Instance->SetBulletId(FGuid::NewGuid());
|
||||
// }
|
||||
//
|
||||
// if (SpawnParameters.ParentId.IsValid() && BulletInstances.Contains(SpawnParameters.ParentId))
|
||||
// {
|
||||
// Instance->SetParentBulletId(SpawnParameters.ParentId);
|
||||
// // if (AGCS_BulletInstance* ParentBulletInstance = BulletInstances[SpawnParameters.ParentId])
|
||||
// // {
|
||||
// // if (ParentBulletInstance->Definition.bUseSharedHitList)
|
||||
// // {
|
||||
// // Instance->HitActors = ParentBulletInstance->HitActors;
|
||||
// // }
|
||||
// // }
|
||||
// }
|
||||
//
|
||||
// if (SpawnParameters.Request)
|
||||
// {
|
||||
// Instance->Request = SpawnParameters.Request;
|
||||
// }
|
||||
//
|
||||
// if (SpawnParameters.Owner)
|
||||
// {
|
||||
// Instance->SetOwner(SpawnParameters.Owner);
|
||||
// }
|
||||
// Instance->SetDefinitionHandle(SpawnParameters.DefinitionHandle);
|
||||
// BulletInstances.Emplace(Instance->BulletId, Instance);
|
||||
// }
|
||||
//
|
||||
// FRotator OriginalRotation = SpawnParameters.SpawnTransform.Rotator();
|
||||
// for (int i = 0; i < RetInstances.Num(); ++i)
|
||||
// {
|
||||
// FTransform ModifiedTransform = SpawnParameters.SpawnTransform;
|
||||
// FRotator RotationYawOffset(Definition.LaunchElevationAngle, Definition.LaunchAngle + Definition.LaunchAngleInterval * i, 0);
|
||||
// ModifiedTransform.SetRotation(UKismetMathLibrary::ComposeRotators(OriginalRotation, RotationYawOffset).Quaternion());
|
||||
// RetInstances[i]->SetActorTransform(ModifiedTransform, false, nullptr, ETeleportType::ResetPhysics);
|
||||
// }
|
||||
//
|
||||
// //batch beginplay.
|
||||
// for (AGCS_BulletInstance* Instance : RetInstances)
|
||||
// {
|
||||
// Instance->OnBulletBeginPlay();
|
||||
// }
|
||||
// return RetInstances;
|
||||
// }
|
||||
@@ -0,0 +1,36 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
|
||||
#include "Bullet/GCS_SphereBulletInstance.h"
|
||||
|
||||
#include "Components/SphereComponent.h"
|
||||
|
||||
|
||||
// Sets default values
|
||||
AGCS_SphereBulletInstance::AGCS_SphereBulletInstance()
|
||||
{
|
||||
// Set this actor to call Tick() every frame. You can turn this off to improve performance if you don't need it.
|
||||
PrimaryActorTick.bCanEverTick = true;
|
||||
|
||||
Sphere = CreateDefaultSubobject<USphereComponent>("Sphere");
|
||||
SetRootComponent(Sphere);
|
||||
}
|
||||
|
||||
UShapeComponent* AGCS_SphereBulletInstance::GetBulletShape_Implementation() const
|
||||
{
|
||||
return Sphere;
|
||||
}
|
||||
|
||||
// Called when the game starts or when spawned
|
||||
void AGCS_SphereBulletInstance::BeginPlay()
|
||||
{
|
||||
Super::BeginPlay();
|
||||
|
||||
}
|
||||
|
||||
// Called every frame
|
||||
void AGCS_SphereBulletInstance::Tick(float DeltaTime)
|
||||
{
|
||||
Super::Tick(DeltaTime);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
|
||||
#include "Collision/DEPRECATED_GCS_CollisionTraceInstance.h"
|
||||
|
||||
#include "GCS_LogChannels.h"
|
||||
#include "Collision/GCS_TraceSystemComponent.h"
|
||||
#include "Components/PrimitiveComponent.h"
|
||||
|
||||
void UDEPRECATED_GCS_CollisionTraceInstance::OnTraceBeginPlay_Implementation()
|
||||
{
|
||||
ActiveTime = 0.0f;
|
||||
HitActors.Empty();
|
||||
}
|
||||
|
||||
void UDEPRECATED_GCS_CollisionTraceInstance::OnTraceEndPlay_Implementation()
|
||||
{
|
||||
ActiveTime = 0.0f;
|
||||
bTraceActive = false;
|
||||
TracePrimitiveComponent = nullptr;
|
||||
TracePrimitiveComponentSocketNames.Empty();
|
||||
HitActors.Empty();
|
||||
}
|
||||
|
||||
void UDEPRECATED_GCS_CollisionTraceInstance::BroadcastHit(const FHitResult& HitResult)
|
||||
{
|
||||
if (!bTraceActive)
|
||||
{
|
||||
UE_LOG(LogGCS_Collision, Warning, TEXT("Hit while inactive,%s"), *TraceOwner->GetName());
|
||||
}
|
||||
}
|
||||
|
||||
void UDEPRECATED_GCS_CollisionTraceInstance::BroadcastStateChanged(bool bNewState)
|
||||
{
|
||||
OnTraceStateChanged(bNewState);
|
||||
}
|
||||
|
||||
void UDEPRECATED_GCS_CollisionTraceInstance::OnTraceTick_Implementation(float DeltaSeconds)
|
||||
{
|
||||
ActiveTime += DeltaSeconds;
|
||||
}
|
||||
|
||||
void UDEPRECATED_GCS_CollisionTraceInstance::OnTraceStateChanged_Implementation(bool bNewState)
|
||||
{
|
||||
bTraceActive = bNewState;
|
||||
HitActors.Empty();
|
||||
}
|
||||
|
||||
void UDEPRECATED_GCS_CollisionTraceInstance::SetTraceMeshInfo(UPrimitiveComponent* NewPrimitiveComponent, TArray<FName> PrimitiveComponentSocketNames)
|
||||
{
|
||||
TracePrimitiveComponent = NewPrimitiveComponent;
|
||||
TracePrimitiveComponentSocketNames = PrimitiveComponentSocketNames;
|
||||
}
|
||||
|
||||
bool UDEPRECATED_GCS_CollisionTraceInstance::CanHitActor_Implementation(const AActor* ActorToCheck) const
|
||||
{
|
||||
//in active trace can not hit anything. TODO make it checkf?
|
||||
if (!bTraceActive)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return ActorToCheck != GetTraceSourceActor() && ActorToCheck != TraceOwner && !HitActors.Contains(ActorToCheck);
|
||||
}
|
||||
|
||||
AActor* UDEPRECATED_GCS_CollisionTraceInstance::GetTraceSourceActor() const
|
||||
{
|
||||
return TracePrimitiveComponent->GetOwner();
|
||||
}
|
||||
|
||||
void UDEPRECATED_GCS_CollisionTraceInstance::ToggleTraceState(bool bNewState)
|
||||
{
|
||||
if (TraceOwner && bTraceActive != bNewState)
|
||||
{
|
||||
BroadcastStateChanged(bNewState);
|
||||
}
|
||||
}
|
||||
|
||||
void UDEPRECATED_GCS_CollisionTraceInstance::OnTraceHit_Implementation(const FHitResult& HitResult)
|
||||
{
|
||||
if (HitResult.GetHitObjectHandle().IsValid())
|
||||
{
|
||||
if (CanHitActor(HitResult.GetActor()))
|
||||
{
|
||||
UE_LOG(LogGCS_Collision, VeryVerbose, TEXT("%s's Trace(%s, SourceActor:%s) hit actor(%s,Comp:%s)"), *TraceOwner->GetName(),
|
||||
*(TraceGameplayTag.IsValid()?TraceGameplayTag.ToString():GetClass()->GetName()),
|
||||
*GetTraceSourceActor()->GetName(),
|
||||
*HitResult.GetActor()->GetName(), *(HitResult.Component.IsValid()?HitResult.GetComponent()->GetName():TEXT("Null")));
|
||||
HitActors.Add(HitResult.GetActor());
|
||||
BroadcastHit(HitResult);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
|
||||
#include "Collision/GCS_AsyncAction_CollisionTrace.h"
|
||||
#include "Collision/GCS_TraceSystemComponent.h"
|
||||
#include "Components/PrimitiveComponent.h"
|
||||
#include "Engine/Engine.h"
|
||||
|
||||
UGCS_AsyncAction_CollisionTrace* UGCS_AsyncAction_CollisionTrace::SetupAndListenForCollisionTraceHit(UGCS_TraceSystemComponent* TraceSystem,
|
||||
const TArray<FGCS_TraceDefinition>& TraceDefinitions,
|
||||
UPrimitiveComponent* PrimitiveComponent,
|
||||
UObject* OptionalSourceObject)
|
||||
{
|
||||
if (TraceSystem == nullptr)
|
||||
{
|
||||
FFrame::KismetExecutionMessage(TEXT("SetupAndListenForCollisionTraceHit was passed a null TraceSystem"), ELogVerbosity::Error);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (PrimitiveComponent == nullptr)
|
||||
{
|
||||
FFrame::KismetExecutionMessage(TEXT("SetupAndListenForCollisionTraceHit was passed a null PrimitiveComponent"), ELogVerbosity::Error);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (TraceDefinitions.IsEmpty())
|
||||
{
|
||||
FFrame::KismetExecutionMessage(TEXT("SetupAndListenForCollisionTraceHit was passed empty TraceDefinitions"), ELogVerbosity::Error);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
UWorld* World = GEngine->GetWorldFromContextObject(TraceSystem, EGetWorldErrorMode::LogAndReturnNull);
|
||||
if (!World)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
UGCS_AsyncAction_CollisionTrace* Action = NewObject<UGCS_AsyncAction_CollisionTrace>();
|
||||
|
||||
Action->TraceDefinitions = TraceDefinitions;
|
||||
Action->TraceSystem = TraceSystem;
|
||||
Action->SourceComponent = PrimitiveComponent;
|
||||
Action->SourceObject = OptionalSourceObject;
|
||||
Action->RegisterWithGameInstance(World);
|
||||
|
||||
return Action;
|
||||
}
|
||||
|
||||
void UGCS_AsyncAction_CollisionTrace::Activate()
|
||||
{
|
||||
UGCS_TraceSystemComponent* TSC = TraceSystem.Get();
|
||||
UPrimitiveComponent* Primitive = SourceComponent.Get();
|
||||
UObject* SourceObj = SourceObject.Get();
|
||||
|
||||
if (TSC && Primitive)
|
||||
{
|
||||
TSC->OnTraceHitEvent.AddDynamic(this, &ThisClass::TraceHitCallback);
|
||||
|
||||
static FHitResult EmptyHitResult;
|
||||
TraceHandles = TSC->AddTraces(TraceDefinitions, Primitive, SourceObj);
|
||||
for (auto& Handle : TraceHandles)
|
||||
{
|
||||
BeforeActive.Broadcast(Handle, EmptyHitResult);
|
||||
TSC->StartTrace(Handle);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
SetReadyToDestroy();
|
||||
}
|
||||
}
|
||||
|
||||
void UGCS_AsyncAction_CollisionTrace::Cancel()
|
||||
{
|
||||
Super::Cancel();
|
||||
UGCS_TraceSystemComponent* TSC = TraceSystem.Get();
|
||||
UPrimitiveComponent* Primitive = SourceComponent.Get();
|
||||
if (TSC && Primitive)
|
||||
{
|
||||
TSC->OnTraceHitEvent.RemoveAll(this);
|
||||
//Deactivate traces.
|
||||
for (const FGCS_TraceHandle& Handle : TraceHandles)
|
||||
{
|
||||
if (Handle.IsValidHandle())
|
||||
{
|
||||
TSC->RemoveTrace(Handle);
|
||||
}
|
||||
}
|
||||
TraceHandles.Empty();
|
||||
SourceComponent = nullptr;
|
||||
TraceSystem = nullptr;
|
||||
SourceObject = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void UGCS_AsyncAction_CollisionTrace::TraceHitCallback(const FGCS_TraceHandle& TraceHandle, const FHitResult& HitResult)
|
||||
{
|
||||
if (ShouldBroadcastDelegates() && TraceHandle.IsValidHandle())
|
||||
{
|
||||
if (TraceHandles.Contains(TraceHandle))
|
||||
{
|
||||
OnHit.Broadcast(TraceHandle, HitResult);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
|
||||
#include "Collision/GCS_TraceDelegates.h"
|
||||
|
||||
#include UE_INLINE_GENERATED_CPP_BY_NAME(GCS_TraceDelegates)
|
||||
@@ -0,0 +1,4 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
|
||||
#include "Collision/GCS_TraceEnumLibrary.h"
|
||||
@@ -0,0 +1,13 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
|
||||
#include "Collision/GCS_TraceFunctionLibrary.h"
|
||||
|
||||
|
||||
TArray<FGCS_TraceDefinition> UGCS_TraceFunctionLibrary::FilterTraceDefinitionsByTag(const TArray<FGCS_TraceDefinition>& Definitions, const FGameplayTag& TagToMatch)
|
||||
{
|
||||
return Definitions.FilterByPredicate([TagToMatch](const FGCS_TraceDefinition& Definition)
|
||||
{
|
||||
return Definition.TraceTag.IsValid() && Definition.CollisionShape.IsValid() && Definition.TraceTag.MatchesTag(TagToMatch);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,249 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
#include "Collision/GCS_TraceStructLibrary.h"
|
||||
|
||||
#include "GCS_LogChannels.h"
|
||||
#include "Components/BoxComponent.h"
|
||||
#include "Components/CapsuleComponent.h"
|
||||
#include "Components/SphereComponent.h"
|
||||
#include "TargetingSystem/TargetingPreset.h"
|
||||
|
||||
|
||||
bool FGCS_CollisionShape::InitializeShape(const UPrimitiveComponent* SourceComponent)
|
||||
{
|
||||
GCS_OWNED_CLOG(SourceComponent, Warning, "Should never use this shape:%s", *FGCS_CollisionShape::StaticStruct()->GetName());
|
||||
FDebug::DumpStackTraceToLog(ELogVerbosity::VeryVerbose);
|
||||
return false;
|
||||
}
|
||||
|
||||
FTransform FGCS_CollisionShape::GetTransform(const UPrimitiveComponent* SourceComponent, const float& Time) const
|
||||
{
|
||||
GCS_OWNED_CLOG(SourceComponent, Warning, "Should never use this shape:%s", *FGCS_CollisionShape::StaticStruct()->GetName());
|
||||
FDebug::DumpStackTraceToLog(ELogVerbosity::VeryVerbose);
|
||||
return SourceComponent->GetComponentTransform();
|
||||
}
|
||||
|
||||
FCollisionShape FGCS_CollisionShape::GetDynamicCollisionShape(const UPrimitiveComponent* SourceComponent, const float& Time) const
|
||||
{
|
||||
GCS_OWNED_CLOG(SourceComponent, Warning, "Should never use this shape:%s", *FGCS_CollisionShape::StaticStruct()->GetName());
|
||||
FDebug::DumpStackTraceToLog(ELogVerbosity::VeryVerbose);
|
||||
return FCollisionShape::MakeSphere(30);
|
||||
}
|
||||
|
||||
bool FGCS_CollisionShape_Static::InitializeShape(const UPrimitiveComponent* SourceComponent)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
FTransform FGCS_CollisionShape_Static::GetTransform(const UPrimitiveComponent* SourceComponent, const float& Time) const
|
||||
{
|
||||
const auto ComponentCurrentTransform = SourceComponent->GetComponentTransform();
|
||||
return FTransform(Orientation, Offset) * ComponentCurrentTransform;
|
||||
}
|
||||
|
||||
FCollisionShape FGCS_CollisionShape_Static::GetDynamicCollisionShape(const UPrimitiveComponent* SourceComponent, const float& Time) const
|
||||
{
|
||||
switch (ShapeType)
|
||||
{
|
||||
case EGCS_CollisionShapeType::Sphere:
|
||||
{
|
||||
return FCollisionShape::MakeSphere(Radius);
|
||||
}
|
||||
case EGCS_CollisionShapeType::Box:
|
||||
{
|
||||
return FCollisionShape::MakeBox(HalfSize);
|
||||
}
|
||||
case EGCS_CollisionShapeType::Capsule:
|
||||
{
|
||||
return FCollisionShape::MakeCapsule(Radius, HalfHeight);
|
||||
}
|
||||
default:
|
||||
{
|
||||
return FCollisionShape::MakeSphere(Radius);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool FGCS_CollisionShape_ShapeBased::InitializeShape(const UPrimitiveComponent* SourceComponent)
|
||||
{
|
||||
if (ShapeType == EGCS_CollisionShapeType::Sphere)
|
||||
{
|
||||
if (const auto SphereCollision = Cast<USphereComponent>(SourceComponent))
|
||||
{
|
||||
Radius = SphereCollision->GetScaledSphereRadius();
|
||||
return true;
|
||||
}
|
||||
GCS_OWNED_CLOG(SourceComponent->GetOwner(), Warning, "No compatible shape component was found! Requires SphereComponent, Got %s(%s)", *GetNameSafe(SourceComponent),
|
||||
*SourceComponent->GetClass()->GetName())
|
||||
}
|
||||
if (ShapeType == EGCS_CollisionShapeType::Box)
|
||||
{
|
||||
if (const auto BoxCollision = Cast<UBoxComponent>(SourceComponent))
|
||||
{
|
||||
HalfSize = BoxCollision->GetScaledBoxExtent();
|
||||
return true;
|
||||
}
|
||||
GCS_OWNED_CLOG(SourceComponent->GetOwner(), Warning, "No compatible shape component was found! Requires BoxComponent, Got %s(%s)", *GetNameSafe(SourceComponent),
|
||||
*SourceComponent->GetClass()->GetName())
|
||||
}
|
||||
if (ShapeType == EGCS_CollisionShapeType::Capsule)
|
||||
{
|
||||
if (const auto CapsuleCollision = Cast<UCapsuleComponent>(SourceComponent))
|
||||
{
|
||||
HalfHeight = CapsuleCollision->GetScaledCapsuleHalfHeight();
|
||||
Radius = CapsuleCollision->GetScaledCapsuleRadius();
|
||||
return true;
|
||||
}
|
||||
GCS_OWNED_CLOG(SourceComponent->GetOwner(), Warning, "No compatible shape component was found! Requires CapsuleComponent, Got %s(%s)", *GetNameSafe(SourceComponent),
|
||||
*SourceComponent->GetClass()->GetName())
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
FTransform FGCS_CollisionShape_ShapeBased::GetTransform(const UPrimitiveComponent* SourceComponent, const float& Time) const
|
||||
{
|
||||
const auto ComponentCurrentTransform = SourceComponent->GetComponentTransform();
|
||||
return FTransform(Orientation, Offset) * ComponentCurrentTransform;
|
||||
}
|
||||
|
||||
FCollisionShape FGCS_CollisionShape_ShapeBased::GetDynamicCollisionShape(const UPrimitiveComponent* SourceComponent, const float& Time) const
|
||||
{
|
||||
switch (ShapeType)
|
||||
{
|
||||
case EGCS_CollisionShapeType::Sphere:
|
||||
{
|
||||
return FCollisionShape::MakeSphere(Radius);
|
||||
}
|
||||
case EGCS_CollisionShapeType::Box:
|
||||
{
|
||||
return FCollisionShape::MakeBox(HalfSize);
|
||||
}
|
||||
case EGCS_CollisionShapeType::Capsule:
|
||||
{
|
||||
return FCollisionShape::MakeCapsule(Radius, HalfHeight);
|
||||
}
|
||||
default:
|
||||
{
|
||||
return FCollisionShape::MakeSphere(Radius);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool FGCS_CollisionShape_Attached::InitializeShape(const UPrimitiveComponent* SourceComponent)
|
||||
{
|
||||
if (!IsValid(SourceComponent) || !SourceComponent->DoesSocketExist(SocketOrBoneName))
|
||||
{
|
||||
GCS_OWNED_CLOG(SourceComponent->GetOwner(), Warning, "No SocketOrBone(%s) exists on mesh component! Got %s(%s)", *SocketOrBoneName.ToString(), *GetNameSafe(SourceComponent),
|
||||
*SourceComponent->GetClass()->GetName())
|
||||
FDebug::DumpStackTraceToLog(ELogVerbosity::VeryVerbose);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
FTransform FGCS_CollisionShape_Attached::GetTransform(const UPrimitiveComponent* SourceComponent, const float& Time) const
|
||||
{
|
||||
const auto BoneTransform = SourceComponent->GetSocketTransform(SocketOrBoneName);
|
||||
return FTransform(Orientation, Offset) * BoneTransform;
|
||||
}
|
||||
|
||||
FTransform FGCS_CollisionShape_SocketBased::GetTransform(const UPrimitiveComponent* SourceComponent, const float& Time) const
|
||||
{
|
||||
const auto ComponentCurrentTransform = SourceComponent->GetComponentTransform();
|
||||
return FTransform(Orientation, Offset) * ComponentCurrentTransform;
|
||||
}
|
||||
|
||||
bool FGCS_CollisionShape_SocketBased::InitializeShape(const UPrimitiveComponent* SourceComponent)
|
||||
{
|
||||
if (const UMeshComponent* MeshComponent = Cast<UMeshComponent>(SourceComponent))
|
||||
{
|
||||
const auto SocketStartTransform = MeshComponent->GetSocketTransform(MeshSocketStart, RTS_Component);
|
||||
const auto SocketStartLocation = SocketStartTransform.GetLocation();
|
||||
|
||||
const auto SocketEndTransform = MeshComponent->GetSocketTransform(MeshSocketEnd, RTS_Component);
|
||||
const auto SocketEndLocation = SocketEndTransform.GetLocation();
|
||||
|
||||
const FVector CenterLocation = (SocketStartLocation + SocketEndLocation) / 2.f;
|
||||
|
||||
HalfHeight = FMath::Max((SocketStartLocation - SocketEndLocation).Length() / 2.f + MeshSocketLengthOffset, 1.f);
|
||||
Offset = CenterLocation;
|
||||
Orientation = FRotationMatrix::MakeFromZ(SocketStartLocation - SocketEndLocation).Rotator();
|
||||
|
||||
return true;
|
||||
}
|
||||
GCS_OWNED_CLOG(SourceComponent->GetOwner(), Warning, "No compatible mesh component was found! Got %s(%s)", *GetNameSafe(SourceComponent),
|
||||
*SourceComponent->GetClass()->GetName())
|
||||
return false;
|
||||
}
|
||||
|
||||
FCollisionShape FGCS_CollisionShape_SocketBased::GetDynamicCollisionShape(const UPrimitiveComponent* SourceComponent, const float& Time) const
|
||||
{
|
||||
return FCollisionShape::MakeCapsule(Radius, HalfHeight);
|
||||
}
|
||||
|
||||
FGCS_TraceDefinition::FGCS_TraceDefinition()
|
||||
{
|
||||
CollisionShape.InitializeAs(FGCS_CollisionShape_Static::StaticStruct());
|
||||
}
|
||||
|
||||
bool FGCS_TraceDefinition::IsValidDefinition() const
|
||||
{
|
||||
// Base collision shape is not allowed!
|
||||
return TraceTag.IsValid() && CollisionShape.IsValid() && CollisionShape.GetScriptStruct() != FGCS_CollisionShape::StaticStruct();
|
||||
}
|
||||
|
||||
FString FGCS_TraceDefinition::ToString() const
|
||||
{
|
||||
return FString::Format(TEXT("{0} {1}"), {TraceTag.ToString(), GetNameSafe(CollisionShape.GetScriptStruct())});
|
||||
}
|
||||
|
||||
bool FGCS_TraceHandle::IsValidHandle() const
|
||||
{
|
||||
return TraceTag.IsValid() && Guid.IsValid() && SourceObject.IsValid();
|
||||
}
|
||||
|
||||
void FGCS_TraceState::ChangeExecutionState(const bool bNewTraceState, const bool bStopImmediate)
|
||||
{
|
||||
if (bNewTraceState)
|
||||
{
|
||||
this->ExecutionState = EGCS_TraceExecutionState::InProgress;
|
||||
this->TimeSinceLastTick = 0;
|
||||
this->TimeSinceActive = 0;
|
||||
this->TotalTickNumDuringExecution = 0;
|
||||
this->HitActors.Reset();
|
||||
if (SourceComponent)
|
||||
{
|
||||
UpdatePreviousTransform(GetCurrentTransform());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (this->ExecutionState == EGCS_TraceExecutionState::InProgress && !bStopImmediate)
|
||||
{
|
||||
this->ExecutionState = EGCS_TraceExecutionState::PendingStop;
|
||||
}
|
||||
else
|
||||
{
|
||||
this->ExecutionState = EGCS_TraceExecutionState::Stopped;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FTransform FGCS_TraceState::GetCurrentTransform() const
|
||||
{
|
||||
check(Shape.IsValid())
|
||||
return Shape.Get<FGCS_CollisionShape>().GetTransform(SourceComponent.Get(), TimeSinceActive);
|
||||
}
|
||||
|
||||
void FGCS_TraceState::UpdatePreviousTransform(const FTransform& Transform)
|
||||
{
|
||||
if (TransformsOverTime.Num() == 0)
|
||||
{
|
||||
TransformsOverTime.Add(Transform);
|
||||
}
|
||||
else
|
||||
{
|
||||
TransformsOverTime[0] = Transform;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,485 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
#include "Collision/GCS_TraceSubsystem.h"
|
||||
|
||||
#include "GCS_LogChannels.h"
|
||||
#include "KismetTraceUtils.h"
|
||||
#include "Async/ParallelFor.h"
|
||||
#include "Components/StaticMeshComponent.h"
|
||||
#include "Collision/GCS_TraceSystemComponent.h"
|
||||
#include "Kismet/KismetMathLibrary.h"
|
||||
|
||||
#define LOCTEXT_NAMESPACE "UGCS_CollisionTraceSubsystem"
|
||||
|
||||
// CVars
|
||||
namespace GenericCombatSystemCVars
|
||||
{
|
||||
#if ENABLE_DRAW_DEBUG
|
||||
|
||||
static bool bEnableTraceDebugging = false;
|
||||
FAutoConsoleVariableRef CvarForceEnableTraceDebugging(
|
||||
TEXT("gcs.debug.EnableTraceDebugging"),
|
||||
bEnableTraceDebugging,
|
||||
TEXT("Toggles whether enable draw debugs for all traces. (Enabled: true, Disabled: false)"));
|
||||
|
||||
static float OverrideTraceDebuggingLifeTime = 0.f;
|
||||
FAutoConsoleVariableRef CvarOverrideTraceDebuggingLifeTime(
|
||||
TEXT("gcs.debug.OverrideTraceDebuggingLifeTime"),
|
||||
OverrideTraceDebuggingLifeTime,
|
||||
TEXT("Overrides the draws life time to ease the trace debugging"));
|
||||
#endif
|
||||
}
|
||||
|
||||
void UGCS_TraceSubsystem::Initialize(FSubsystemCollectionBase& Collection)
|
||||
{
|
||||
Super::Initialize(Collection);
|
||||
this->TraceStates.Reserve(4068);
|
||||
}
|
||||
|
||||
void UGCS_TraceSubsystem::Tick(float DeltaTime)
|
||||
{
|
||||
DECLARE_SCOPE_CYCLE_COUNTER(TEXT("UGCS_TraceSubsystem::Tick"), STAT_UGCS_TraceSubsystem_Tick, STATGROUP_GCS)
|
||||
TRACE_CPUPROFILER_EVENT_SCOPE_STR(__FUNCTION__)
|
||||
|
||||
TickIdx++;
|
||||
// Lock removals so we don't get any modifications to the array while we are iterating
|
||||
RemovalLock = true;
|
||||
|
||||
PreTraceTick(DeltaTime);
|
||||
|
||||
PrepareSubTicks(DeltaTime);
|
||||
PerformSubTicks(DeltaTime);
|
||||
|
||||
PostTraceTick();
|
||||
|
||||
// Reverse iterate, remove pending removals
|
||||
RemovalLock = false;
|
||||
for (int Idx = TraceStates.Num() - 1; Idx >= 0; Idx--)
|
||||
{
|
||||
const auto& TraceState = TraceStates[Idx];
|
||||
if (!TraceStates.IsValidIndex(Idx))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (TraceState.IsPendingRemoval)
|
||||
{
|
||||
RemoveTraceStateAt(Idx, TraceState.Handle.Guid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UGCS_TraceSubsystem::RemoveTraceState(const int Idx, const FGuid Guid)
|
||||
{
|
||||
if (RemovalLock)
|
||||
{
|
||||
if (TraceStates.IsValidIndex(Idx) && TraceStates[Idx].Handle.Guid == Guid)
|
||||
{
|
||||
TraceStates[Idx].IsPendingRemoval = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
RemoveTraceStateAt(Idx, Guid);
|
||||
}
|
||||
}
|
||||
|
||||
int32 UGCS_TraceSubsystem::AddTraceState()
|
||||
{
|
||||
FScopeLock ScopeLock(&CriticalSection);
|
||||
return TraceStates.AddDefaulted();
|
||||
}
|
||||
|
||||
bool UGCS_TraceSubsystem::IsValidStateIdx(int32 StateIdx) const
|
||||
{
|
||||
return TraceStates.IsValidIndex(StateIdx);
|
||||
}
|
||||
|
||||
FGCS_TraceState& UGCS_TraceSubsystem::GetTraceStateAt(const int Index)
|
||||
{
|
||||
return TraceStates[Index];
|
||||
}
|
||||
|
||||
void UGCS_TraceSubsystem::RemoveTraceStateAt(const int Idx, const FGuid Guid)
|
||||
{
|
||||
DECLARE_SCOPE_CYCLE_COUNTER(TEXT("UGCS_TraceSubsystem::RemoveTraceStateAt"), STAT_UGCS_TraceSubsystem_RemoveTraceStateAt, STATGROUP_GCS)
|
||||
TRACE_CPUPROFILER_EVENT_SCOPE_STR(__FUNCTION__)
|
||||
FScopeLock ScopeLock(&CriticalSection);
|
||||
if (TraceStates.IsValidIndex(Idx) && TraceStates[Idx].Handle.Guid == Guid)
|
||||
{
|
||||
// 移除并交换
|
||||
TraceStates.RemoveAtSwap(Idx);
|
||||
|
||||
// If a state was moved to Idx, update its corresponding handle in the OwningSystem
|
||||
if (TraceStates.IsValidIndex(Idx))
|
||||
{
|
||||
FGCS_TraceState& MovedState = TraceStates[Idx];
|
||||
if (IsValid(MovedState.OwningSystem))
|
||||
{
|
||||
// Update the Component's HandleToStateIdx map
|
||||
MovedState.OwningSystem->HandleToStateIdx.Remove(MovedState.Handle); // Remove old mapping
|
||||
MovedState.OwningSystem->HandleToStateIdx.Add(MovedState.Handle, Idx); // Add new mapping
|
||||
}
|
||||
// mark pending removal if owning system become invalid.
|
||||
else
|
||||
{
|
||||
MovedState.IsPendingRemoval = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UGCS_TraceSubsystem::PreTraceTick(const float DeltaTime)
|
||||
{
|
||||
DECLARE_SCOPE_CYCLE_COUNTER(TEXT("UGCS_TraceSubsystem::PreTraceTick"), STAT_UGCS_TraceSubsystem_PreTraceTick, STATGROUP_GCS)
|
||||
TRACE_CPUPROFILER_EVENT_SCOPE_STR(__FUNCTION__)
|
||||
ParallelFor(TraceStates.Num(), [&](const int32 Idx)
|
||||
{
|
||||
if (!TraceStates.IsValidIndex(Idx))
|
||||
{
|
||||
return;
|
||||
}
|
||||
auto& TraceState = TraceStates[Idx];
|
||||
if (TraceState.ExecutionState == EGCS_TraceExecutionState::Stopped)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
check(TraceState.bShouldTickThisFrame == false)
|
||||
|
||||
if (!IsValid(TraceState.SourceComponent))
|
||||
{
|
||||
TraceState.ExecutionState = EGCS_TraceExecutionState::Stopped;
|
||||
TraceState.bShouldTickThisFrame = false;
|
||||
return;
|
||||
}
|
||||
|
||||
const FTransform& CurrentTransform = TraceState.GetCurrentTransform();
|
||||
|
||||
if (TraceState.TransformsOverTime.IsEmpty())
|
||||
{
|
||||
TraceState.TransformsOverTime.Add(CurrentTransform);
|
||||
}
|
||||
|
||||
if (TraceState.ExecutionState == EGCS_TraceExecutionState::PendingStop)
|
||||
{
|
||||
TraceState.bShouldTickThisFrame = true;
|
||||
TraceState.TransformsOverTime.Add(CurrentTransform);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (TraceState.TickPolicy)
|
||||
{
|
||||
case EGCS_TraceTickType::Default:
|
||||
TraceState.TransformsOverTime.Add(CurrentTransform);
|
||||
TraceState.bShouldTickThisFrame = true;
|
||||
return;
|
||||
case EGCS_TraceTickType::DistanceBased:
|
||||
{
|
||||
const FVector& PreviousLocation = TraceState.TransformsOverTime[0].GetLocation();
|
||||
const FVector& CurrentLocation = CurrentTransform.GetLocation();
|
||||
const FRotator& PreviousRotation = TraceState.TransformsOverTime[0].GetRotation().Rotator();
|
||||
const FRotator& CurrentRotation = CurrentTransform.GetRotation().Rotator();
|
||||
|
||||
bool bDistanceThresholdMet = (PreviousLocation - CurrentLocation).Length() >= TraceState.TickInterval;
|
||||
bool bAngleThresholdMet = FMath::Abs(PreviousRotation.Yaw - CurrentRotation.Yaw) >= TraceState.AngleThreshold ||
|
||||
FMath::Abs(PreviousRotation.Pitch - CurrentRotation.Pitch) >= TraceState.AngleThreshold ||
|
||||
FMath::Abs(PreviousRotation.Roll - CurrentRotation.Roll) >= TraceState.AngleThreshold;
|
||||
|
||||
if (bDistanceThresholdMet || bAngleThresholdMet)
|
||||
{
|
||||
TraceState.TransformsOverTime.Add(CurrentTransform);
|
||||
TraceState.bShouldTickThisFrame = true;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
case EGCS_TraceTickType::FixedFrameRate:
|
||||
TraceState.TimeSinceLastTick += DeltaTime;
|
||||
int32 SubTickCountPreview = FMath::FloorToInt(TraceState.TimeSinceLastTick / TraceState.TickInterval);
|
||||
if (SubTickCountPreview > 0)
|
||||
{
|
||||
TraceState.TransformsOverTime.Add(CurrentTransform);
|
||||
TraceState.bShouldTickThisFrame = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void UGCS_TraceSubsystem::PostTraceTick()
|
||||
{
|
||||
DECLARE_SCOPE_CYCLE_COUNTER(TEXT("UGCS_TraceSubsystem::PostTraceTick"), STAT_UGCS_TraceSubsystem_PostTraceTick, STATGROUP_GCS)
|
||||
TRACE_CPUPROFILER_EVENT_SCOPE_STR(__FUNCTION__)
|
||||
ParallelFor(TraceStates.Num(), [&](const int32 Idx)
|
||||
{
|
||||
auto& TraceState = TraceStates[Idx];
|
||||
if (!TraceState.bShouldTickThisFrame)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Reset time since last tick.
|
||||
if (TraceState.TickPolicy == EGCS_TraceTickType::FixedFrameRate)
|
||||
{
|
||||
int32 SubTickCount = TraceState.SubTicks.Num();
|
||||
TraceState.TimeSinceLastTick -= SubTickCount * TraceState.TickInterval;
|
||||
|
||||
TraceState.TimeSinceLastTick = FMath::Max(TraceState.TimeSinceLastTick, 0.0f);
|
||||
}
|
||||
else
|
||||
{
|
||||
TraceState.TimeSinceLastTick = 0;
|
||||
}
|
||||
|
||||
TraceState.bShouldTickThisFrame = false;
|
||||
|
||||
if (TraceState.ExecutionState == EGCS_TraceExecutionState::PendingStop)
|
||||
{
|
||||
TraceState.ExecutionState = EGCS_TraceExecutionState::Stopped;
|
||||
TraceState.TransformsOverTime.Empty();
|
||||
TraceState.TimeSinceActive = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!TraceState.TransformsOverTime.IsEmpty())
|
||||
{
|
||||
TraceState.TransformsOverTime.RemoveAtSwap(0);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void UGCS_TraceSubsystem::PrepareSubTicks(const float DeltaTime)
|
||||
{
|
||||
DECLARE_SCOPE_CYCLE_COUNTER(TEXT("UGCS_TraceSubsystem::PrepareSubTicks"), STAT_UGCS_TraceSubsystem_PrepareSubTicks, STATGROUP_GCS)
|
||||
TRACE_CPUPROFILER_EVENT_SCOPE_STR(__FUNCTION__)
|
||||
|
||||
ParallelFor(TraceStates.Num(), [&](const int32 Idx)
|
||||
{
|
||||
if (!TraceStates.IsValidIndex(Idx))
|
||||
{
|
||||
return;
|
||||
}
|
||||
auto& TraceState = TraceStates[Idx];
|
||||
|
||||
TraceState.SubTicks.Reset();
|
||||
if (TraceState.bShouldTickThisFrame)
|
||||
{
|
||||
int32 SubTickCount = 1;
|
||||
if (TraceState.TickPolicy == EGCS_TraceTickType::DistanceBased)
|
||||
{
|
||||
SubTickCount = FMath::CeilToInt((TraceState.TransformsOverTime[0].GetLocation() - TraceState.TransformsOverTime[1].GetLocation()).Length() / TraceState.TickInterval);
|
||||
}
|
||||
else if (TraceState.TickPolicy == EGCS_TraceTickType::FixedFrameRate)
|
||||
{
|
||||
SubTickCount = FMath::FloorToInt(TraceState.TimeSinceLastTick / TraceState.TickInterval);
|
||||
}
|
||||
SubTickCount = FMath::Min(10, SubTickCount);
|
||||
|
||||
if (TraceState.TransformsOverTime.Num() > 1)
|
||||
{
|
||||
const float SubTickRatio = 1.0 / SubTickCount;
|
||||
const FTransform CurrentTransform = TraceState.TransformsOverTime.Last();
|
||||
const FTransform PreviousTransform = TraceState.TransformsOverTime[0];
|
||||
TraceState.CollisionShapeOverTime = TraceState.Shape.Get<FGCS_CollisionShape>().GetDynamicCollisionShape(TraceState.SourceComponent, TraceState.TimeSinceActive);
|
||||
for (int32 i = 0; i < SubTickCount; i++)
|
||||
{
|
||||
FGCS_TraceSubTick SubTick{};
|
||||
SubTick.StartTransform = UKismetMathLibrary::TLerp(PreviousTransform, CurrentTransform, SubTickRatio * i, ELerpInterpolationMode::DualQuatInterp);
|
||||
SubTick.EndTransform = UKismetMathLibrary::TLerp(PreviousTransform, CurrentTransform, SubTickRatio * (i + 1), ELerpInterpolationMode::DualQuatInterp);
|
||||
SubTick.AverageTransform = UKismetMathLibrary::TLerp(SubTick.StartTransform, SubTick.EndTransform, 0.5, ELerpInterpolationMode::DualQuatInterp);
|
||||
TraceState.SubTicks.Add(SubTick);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
GCS_LOG(Warning, "Trace [%s] has less than 2 transforms in TransformsOverTime array!", *TraceState.Handle.TraceTag.ToString())
|
||||
}
|
||||
}
|
||||
|
||||
if (TraceState.ExecutionState == EGCS_TraceExecutionState::InProgress)
|
||||
{
|
||||
TraceState.TimeSinceActive += DeltaTime;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void UGCS_TraceSubsystem::PerformSubTicks(const float DeltaTime)
|
||||
{
|
||||
DECLARE_SCOPE_CYCLE_COUNTER(TEXT("UGCS_TraceSubsystem::PerformSubTicks"), STAT_UGCS_TraceSubsystem_PerformSubTicks, STATGROUP_GCS)
|
||||
TRACE_CPUPROFILER_EVENT_SCOPE_STR(__FUNCTION__)
|
||||
|
||||
for (int32 Idx = 0; Idx < TraceStates.Num(); Idx++)
|
||||
{
|
||||
if (!TraceStates.IsValidIndex(Idx))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
auto& TraceState = TraceStates[Idx];
|
||||
|
||||
if (TraceState.bShouldTickThisFrame)
|
||||
{
|
||||
for (int32 SubTickIdx = 0; SubTickIdx < TraceState.SubTicks.Num(); SubTickIdx++)
|
||||
{
|
||||
const FGCS_TraceSubTick& SubTick = TraceState.SubTicks[SubTickIdx];
|
||||
|
||||
// Respect cancellations by user-defined code immediately.
|
||||
if (TraceState.ExecutionState == EGCS_TraceExecutionState::Stopped)
|
||||
{
|
||||
break;
|
||||
}
|
||||
TraceState.TotalTickNumDuringExecution += 1;
|
||||
|
||||
FTraceDelegate Delegate = FTraceDelegate::CreateUObject(this, &UGCS_TraceSubsystem::HandleTraceResults, Idx, TickIdx, TraceState.TimeSinceActive);
|
||||
|
||||
PerformAsyncTrace(SubTick.StartTransform, SubTick.EndTransform, SubTick.AverageTransform, TraceState.World, TraceState.SweepSetting,
|
||||
TraceState.CollisionShapeOverTime,
|
||||
TraceState.CollisionParams,
|
||||
TraceState.ResponseParams,
|
||||
TraceState.ObjectQueryParams, &Delegate);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UGCS_TraceSubsystem::HandleTraceResults(const FTraceHandle& InTraceHandle, FTraceDatum& InTraceDatum, int32 InTraceStateIdx, uint32 InTickIdx, float InShapeTime)
|
||||
{
|
||||
DECLARE_SCOPE_CYCLE_COUNTER(TEXT("UGCS_TraceSubsystem::HandleTraceResults"), STAT_UGCS_TraceSubsystem_HandleTraceResults, STATGROUP_GCS)
|
||||
TRACE_CPUPROFILER_EVENT_SCOPE_STR(__FUNCTION__)
|
||||
|
||||
if (!TraceStates.IsValidIndex(InTraceStateIdx))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
auto& TraceState = TraceStates[InTraceStateIdx];
|
||||
|
||||
if (!IsValid(TraceState.OwningSystem) || TraceState.ExecutionState == EGCS_TraceExecutionState::Stopped)
|
||||
{
|
||||
TraceState.TimeSinceActive = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
// GCS_LOG(Warning, "Trace [%s] has ticked %d within %f s", *TraceState.Handle.TraceTag.ToString(), TraceState.TotalTickNumDuringExecution, TraceState.TimeSinceActive)
|
||||
|
||||
#if ENABLE_DRAW_DEBUG
|
||||
if (GenericCombatSystemCVars::bEnableTraceDebugging)
|
||||
{
|
||||
const EDrawDebugTrace::Type DrawDebugType = GenericCombatSystemCVars::OverrideTraceDebuggingLifeTime > 0 ? EDrawDebugTrace::ForDuration : EDrawDebugTrace::ForOneFrame;
|
||||
const float DrawDebugTime = GenericCombatSystemCVars::OverrideTraceDebuggingLifeTime > 0
|
||||
? GenericCombatSystemCVars::OverrideTraceDebuggingLifeTime
|
||||
: (DrawDebugType == EDrawDebugTrace::ForOneFrame ? 0.0f : 0.5f);
|
||||
|
||||
const bool bHasAuthority = TraceState.SourceComponent->GetOwner()->HasAuthority();
|
||||
|
||||
|
||||
DrawDebug(InTraceDatum.Start,
|
||||
InTraceDatum.End,
|
||||
InTraceDatum.Rot,
|
||||
InTraceDatum.OutHits, TraceState.Shape.Get<FGCS_CollisionShape>().GetDynamicCollisionShape(TraceState.SourceComponent, InShapeTime), TraceState.World,
|
||||
DrawDebugType,
|
||||
DrawDebugTime,
|
||||
bHasAuthority ? FLinearColor::Red : FLinearColor::White,
|
||||
bHasAuthority ? FLinearColor::Green : FLinearColor::Black);
|
||||
// GCS_OWNED_CLOG(TraceState.SourceComponent, Display, "Did collision trace: start(%s) end(%s)", *InTraceDatum.Start.ToCompactString(), *InTraceDatum.End.ToCompactString())
|
||||
}
|
||||
#endif // ENABLE_DRAW_DEBUG
|
||||
|
||||
TArray<FHitResult> FilteredHits;
|
||||
for (const FHitResult& NewHit : InTraceDatum.OutHits)
|
||||
{
|
||||
if (!TraceState.HitActors.Contains(NewHit.GetActor()))
|
||||
{
|
||||
FilteredHits.Add(NewHit);
|
||||
TraceState.HitActors.Add(NewHit.GetActor());
|
||||
}
|
||||
}
|
||||
if (FilteredHits.Num() > 0)
|
||||
{
|
||||
TraceState.OwningSystem->OnTraceHitDetected(TraceState.Handle, FilteredHits, TraceState.TimeSinceLastTick, InTickIdx);
|
||||
}
|
||||
}
|
||||
|
||||
void UGCS_TraceSubsystem::PerformAsyncTrace(const FTransform& StartTransform, const FTransform& EndTransform, const FTransform& AverageTransform, UWorld* World,
|
||||
const FGCS_TraceSweepSetting& TraceSettings, const FCollisionShape& CollisionShape, const FCollisionQueryParams& CollisionParams,
|
||||
const FCollisionResponseParams& CollisionResponseParams,
|
||||
const FCollisionObjectQueryParams& ObjectQueryParams, const FTraceDelegate* InDelegate)
|
||||
{
|
||||
switch (TraceSettings.SweepType)
|
||||
{
|
||||
case EGCS_TraceSweepType::ByChannel:
|
||||
World->AsyncSweepByChannel(
|
||||
EAsyncTraceType::Multi,
|
||||
StartTransform.GetLocation(),
|
||||
EndTransform.GetLocation(),
|
||||
AverageTransform.GetRotation(),
|
||||
TraceSettings.TraceChannel,
|
||||
CollisionShape,
|
||||
CollisionParams,
|
||||
CollisionResponseParams, InDelegate);
|
||||
break;
|
||||
case EGCS_TraceSweepType::ByObject:
|
||||
World->AsyncSweepByObjectType(
|
||||
EAsyncTraceType::Multi,
|
||||
StartTransform.GetLocation(),
|
||||
EndTransform.GetLocation(),
|
||||
AverageTransform.GetRotation(),
|
||||
ObjectQueryParams,
|
||||
CollisionShape,
|
||||
CollisionParams, InDelegate);
|
||||
break;
|
||||
case EGCS_TraceSweepType::ByProfile:
|
||||
World->AsyncSweepByProfile(
|
||||
EAsyncTraceType::Multi,
|
||||
StartTransform.GetLocation(),
|
||||
EndTransform.GetLocation(),
|
||||
AverageTransform.GetRotation(),
|
||||
TraceSettings.ProfileName,
|
||||
CollisionShape,
|
||||
CollisionParams, InDelegate);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
#if ENABLE_DRAW_DEBUG
|
||||
void UGCS_TraceSubsystem::DrawDebug(const FVector& StartLocation, const FVector& EndLocation, const FQuat& Orientation, TArray<FHitResult> Hits, const FCollisionShape& CollisionShape,
|
||||
const UWorld* World,
|
||||
const EDrawDebugTrace::Type DrawDebugType, float DrawDebugTime, const FLinearColor& DrawDebugColor,
|
||||
const FLinearColor& DrawDebugHitColor)
|
||||
{
|
||||
DECLARE_SCOPE_CYCLE_COUNTER(TEXT("UGCS_TraceSubsystem::DrawDebug"), STAT_UGCS_TraceSubsystem_DrawDebug, STATGROUP_GCS)
|
||||
TRACE_CPUPROFILER_EVENT_SCOPE_STR(__FUNCTION__)
|
||||
|
||||
// We have to manually find if there is a blocking hit.
|
||||
bool bHasBlockingHit = false;
|
||||
for (const FHitResult& HitResult : Hits)
|
||||
{
|
||||
if (HitResult.bBlockingHit)
|
||||
{
|
||||
bHasBlockingHit = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
switch (CollisionShape.ShapeType)
|
||||
{
|
||||
case ECollisionShape::Sphere:
|
||||
DrawDebugSphereTraceMulti(World, StartLocation, EndLocation, CollisionShape.GetSphereRadius(), DrawDebugType, bHasBlockingHit, Hits, DrawDebugColor, DrawDebugHitColor, DrawDebugTime);
|
||||
break;
|
||||
case ECollisionShape::Capsule:
|
||||
DrawDebugCapsuleTraceMulti(World, StartLocation, EndLocation, CollisionShape.GetCapsuleRadius(), CollisionShape.GetCapsuleHalfHeight(), Orientation.Rotator(), DrawDebugType,
|
||||
bHasBlockingHit, Hits, DrawDebugColor, DrawDebugHitColor, DrawDebugTime);
|
||||
break;
|
||||
case ECollisionShape::Box:
|
||||
DrawDebugBoxTraceMulti(World, StartLocation, EndLocation, CollisionShape.GetBox(), Orientation.Rotator(), DrawDebugType, bHasBlockingHit, Hits, DrawDebugColor, DrawDebugHitColor,
|
||||
DrawDebugTime);
|
||||
break;
|
||||
default:
|
||||
case ECollisionShape::Line:
|
||||
DrawDebugLineTraceMulti(World, StartLocation, EndLocation, DrawDebugType, bHasBlockingHit, Hits, DrawDebugColor, DrawDebugHitColor, DrawDebugTime);
|
||||
break;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#undef LOCTEXT_NAMESPACE
|
||||
@@ -0,0 +1,489 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
#include "Collision/GCS_TraceSystemComponent.h"
|
||||
#include "GCS_LogChannels.h"
|
||||
#include "Collision/DEPRECATED_GCS_CollisionTraceInstance.h"
|
||||
#include "Collision/GCS_TraceSubsystem.h"
|
||||
#include "Components/SkeletalMeshComponent.h"
|
||||
#include "Components/PrimitiveComponent.h"
|
||||
#include "Utility/GCS_CombatFunctionLibrary.h"
|
||||
|
||||
UGCS_TraceSystemComponent::UGCS_TraceSystemComponent(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer)
|
||||
{
|
||||
PrimaryComponentTick.bCanEverTick = false;
|
||||
SetIsReplicatedByDefault(false);
|
||||
}
|
||||
|
||||
UGCS_TraceSystemComponent* UGCS_TraceSystemComponent::GetTraceSystemComponent(const AActor* Actor)
|
||||
{
|
||||
return IsValid(Actor) ? Actor->FindComponentByClass<UGCS_TraceSystemComponent>() : nullptr;
|
||||
}
|
||||
|
||||
bool UGCS_TraceSystemComponent::FindTraceSystemComponent(const AActor* Actor, UGCS_TraceSystemComponent*& Component)
|
||||
{
|
||||
Component = GetTraceSystemComponent(Actor);
|
||||
return Component != nullptr;
|
||||
}
|
||||
|
||||
void UGCS_TraceSystemComponent::OnInitialize_Implementation()
|
||||
{
|
||||
if (UMeshComponent* PrimitiveComp = UGCS_CombatFunctionLibrary::GetMainMeshComponent(GetOwner()))
|
||||
{
|
||||
AddTraces(TraceDefinitions, PrimitiveComp, GetOwner());
|
||||
}
|
||||
|
||||
bInitialized = true;
|
||||
}
|
||||
|
||||
void UGCS_TraceSystemComponent::OnDeinitialize_Implementation()
|
||||
{
|
||||
if (bInitialized)
|
||||
{
|
||||
RemoveAllTraces();
|
||||
bInitialized = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Called when the game starts
|
||||
void UGCS_TraceSystemComponent::BeginPlay()
|
||||
{
|
||||
if (bAutoInitialize)
|
||||
{
|
||||
OnInitialize();
|
||||
}
|
||||
Super::BeginPlay();
|
||||
}
|
||||
|
||||
void UGCS_TraceSystemComponent::EndPlay(const EEndPlayReason::Type EndPlayReason)
|
||||
{
|
||||
Super::EndPlay(EndPlayReason);
|
||||
OnDeinitialize();
|
||||
}
|
||||
|
||||
void UGCS_TraceSystemComponent::OnComponentDestroyed(bool bDestroyingHierarchy)
|
||||
{
|
||||
Super::OnComponentDestroyed(bDestroyingHierarchy);
|
||||
OnDestroyedEvent.Broadcast();
|
||||
|
||||
OnDeinitialize();
|
||||
}
|
||||
|
||||
TArray<FGCS_TraceHandle> UGCS_TraceSystemComponent::AddTraces(const TArray<FGCS_TraceDefinition>& Definitions, UPrimitiveComponent* SourceComponent, UObject* SourceObject)
|
||||
{
|
||||
TArray<FGCS_TraceHandle> Handles;
|
||||
for (const FGCS_TraceDefinition& Def : Definitions)
|
||||
{
|
||||
FGCS_TraceHandle Handle = AddTrace(Def, SourceComponent, SourceObject);
|
||||
if (Handle.IsValidHandle())
|
||||
{
|
||||
Handles.Add(Handle);
|
||||
}
|
||||
}
|
||||
return Handles;
|
||||
}
|
||||
|
||||
TArray<FGCS_TraceHandle> UGCS_TraceSystemComponent::AddTraces(const TArray<FDataTableRowHandle>& DefinitionHandles, UPrimitiveComponent* SourceComponent, UObject* SourceObject)
|
||||
{
|
||||
TArray<FGCS_TraceHandle> Handles;
|
||||
for (const FDataTableRowHandle& DefHandle : DefinitionHandles)
|
||||
{
|
||||
FGCS_TraceHandle Handle = AddTrace(DefHandle, SourceComponent, SourceObject);
|
||||
if (Handle.IsValidHandle())
|
||||
{
|
||||
Handles.Add(Handle);
|
||||
}
|
||||
}
|
||||
return Handles;
|
||||
}
|
||||
|
||||
FGCS_TraceHandle UGCS_TraceSystemComponent::AddTrace(const FGCS_TraceDefinition& TraceDefinition, UPrimitiveComponent* SourceComponent, UObject* SourceObject)
|
||||
{
|
||||
if (!TraceDefinition.IsValidDefinition())
|
||||
{
|
||||
GCS_CLOG(Warning, "Try adding invalid trace definition!")
|
||||
return FGCS_TraceHandle();
|
||||
}
|
||||
|
||||
if (!IsValid(SourceObject))
|
||||
{
|
||||
GCS_CLOG(Warning, "Missing source object for trace definition:%s", *TraceDefinition.ToString())
|
||||
return FGCS_TraceHandle();
|
||||
}
|
||||
|
||||
if (!IsValid(SourceComponent))
|
||||
{
|
||||
GCS_CLOG(Warning, "Missing source component for trace definition:%s", *TraceDefinition.ToString())
|
||||
return FGCS_TraceHandle();
|
||||
}
|
||||
|
||||
FInstancedStruct Shape = TraceDefinition.CollisionShape;
|
||||
bool bWasInitialized = Shape.GetMutable<FGCS_CollisionShape>().InitializeShape(SourceComponent);
|
||||
|
||||
if (!bWasInitialized)
|
||||
{
|
||||
return FGCS_TraceHandle();
|
||||
}
|
||||
|
||||
UWorld* World = GetWorld();
|
||||
if (!IsValid(World))
|
||||
{
|
||||
GCS_CLOG(Error, "Invalid world context!")
|
||||
return FGCS_TraceHandle();
|
||||
}
|
||||
|
||||
UGCS_TraceSubsystem* Subsystem = World->GetSubsystem<UGCS_TraceSubsystem>();
|
||||
if (!IsValid(Subsystem))
|
||||
{
|
||||
GCS_CLOG(Error, "Failed to get TraceSubsystem!")
|
||||
return FGCS_TraceHandle();
|
||||
}
|
||||
int32 StateIdx = Subsystem->AddTraceState();
|
||||
|
||||
FGCS_TraceHandle Handle = {TraceDefinition.TraceTag, FGuid::NewGuid(), SourceObject};
|
||||
|
||||
float TickInterval = 0;
|
||||
float AngleThreshold = TraceDefinition.AngleTickThreshold;
|
||||
if (TraceDefinition.TraceTickType == EGCS_TraceTickType::DistanceBased)
|
||||
{
|
||||
TickInterval = TraceDefinition.DistanceTickThreshold;
|
||||
}
|
||||
else if (TraceDefinition.TraceTickType == EGCS_TraceTickType::FixedFrameRate)
|
||||
{
|
||||
TickInterval = 1.0f / static_cast<float>(TraceDefinition.FixedTickFrameRate);
|
||||
}
|
||||
auto& TraceState = Subsystem->GetTraceStateAt(StateIdx);
|
||||
TraceState.World = GetWorld();
|
||||
TraceState.SourceComponent = SourceComponent;
|
||||
TraceState.OwningSystem = this;
|
||||
|
||||
TraceState.SweepSetting = TraceDefinition.SweepSetting;
|
||||
TraceState.Shape = Shape;
|
||||
|
||||
TraceState.Handle = Handle;
|
||||
TraceState.ExecutionState = EGCS_TraceExecutionState::Stopped;
|
||||
TraceState.TimeSinceLastTick = 0;
|
||||
TraceState.TimeSinceActive = 0;
|
||||
TraceState.TickPolicy = TraceDefinition.TraceTickType;
|
||||
TraceState.TickInterval = TickInterval;
|
||||
TraceState.AngleThreshold = AngleThreshold;
|
||||
TraceState.bShouldTickThisFrame = false;
|
||||
if (AActor* Owner = GetOwner())
|
||||
{
|
||||
TraceState.CollisionParams.AddIgnoredActor(Owner);
|
||||
}
|
||||
TraceState.CollisionParams.bTraceComplex = TraceDefinition.SweepSetting.bTraceComplex;
|
||||
TraceState.CollisionParams.bReturnPhysicalMaterial = true;
|
||||
for (const auto& ObjType : TraceDefinition.SweepSetting.ObjectTypes)
|
||||
{
|
||||
TraceState.ObjectQueryParams.AddObjectTypesToQuery(ObjType);
|
||||
}
|
||||
|
||||
HandleToStateIdx.Add(Handle, StateIdx);
|
||||
TagToHandles.Add(TraceDefinition.TraceTag, Handle);
|
||||
GCS_CLOG(Verbose, "Added trace(%s) with source component:%s", *Handle.ToDebugString(), *GetNameSafe(SourceComponent))
|
||||
return Handle;
|
||||
}
|
||||
|
||||
FGCS_TraceHandle UGCS_TraceSystemComponent::AddTrace(const FDataTableRowHandle& TraceDefinitionHandle, UPrimitiveComponent* SourceComponent, UObject* SourceObject)
|
||||
{
|
||||
if (!TraceDefinitionHandle.IsNull())
|
||||
{
|
||||
if (FGCS_TraceDefinition* TraceDefinition = TraceDefinitionHandle.GetRow<FGCS_TraceDefinition>(TEXT("AddTrace")))
|
||||
{
|
||||
AddTrace(*TraceDefinition, SourceComponent, SourceObject);
|
||||
}
|
||||
else
|
||||
{
|
||||
GCS_CLOG(Warning, "definition handle doesn't point to valid TraceDefinition Table. %s", *TraceDefinitionHandle.ToDebugString())
|
||||
}
|
||||
}
|
||||
GCS_CLOG(Warning, "Passed in invalid trace definition handle! %s", *TraceDefinitionHandle.ToDebugString())
|
||||
return FGCS_TraceHandle();
|
||||
}
|
||||
|
||||
void UGCS_TraceSystemComponent::RemoveTraces(const TArray<FGCS_TraceHandle>& TraceHandles)
|
||||
{
|
||||
for (const FGCS_TraceHandle& TraceHandle : TraceHandles)
|
||||
{
|
||||
RemoveTrace(TraceHandle);
|
||||
}
|
||||
}
|
||||
|
||||
void UGCS_TraceSystemComponent::RemoveTrace(const FGCS_TraceHandle& TraceHandle)
|
||||
{
|
||||
if (TraceHandle.IsValidHandle())
|
||||
{
|
||||
if (const int32* StateIdx = HandleToStateIdx.Find(TraceHandle))
|
||||
{
|
||||
UGCS_TraceSubsystem* Subsystem = GetWorld()->GetSubsystem<UGCS_TraceSubsystem>();
|
||||
if (IsValid(Subsystem) && Subsystem->IsValidStateIdx(*StateIdx))
|
||||
{
|
||||
auto& State = Subsystem->GetTraceStateAt(*StateIdx);
|
||||
State.ChangeExecutionState(false);
|
||||
OnTraceStateChanged(TraceHandle, false);
|
||||
Subsystem->RemoveTraceState(*StateIdx, TraceHandle.Guid);
|
||||
GCS_CLOG(Verbose, "Removed trace(%s)", *TraceHandle.ToDebugString())
|
||||
}
|
||||
HandleToStateIdx.Remove(TraceHandle);
|
||||
TagToHandles.RemoveSingle(TraceHandle.TraceTag, TraceHandle);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TArray<FGCS_TraceHandle> UGCS_TraceSystemComponent::GetTraceHandlesBySource(const UObject* SourceObject) const
|
||||
{
|
||||
TArray<FGCS_TraceHandle> Handles;
|
||||
for (const auto& Pair : HandleToStateIdx)
|
||||
{
|
||||
if (Pair.Key.SourceObject == SourceObject)
|
||||
{
|
||||
Handles.Add(Pair.Key);
|
||||
}
|
||||
}
|
||||
return Handles;
|
||||
}
|
||||
|
||||
TArray<FGCS_TraceHandle> UGCS_TraceSystemComponent::GetTraceHandlesByTagsAndSource(const FGameplayTagContainer& TraceTags, const UObject* SourceObject) const
|
||||
{
|
||||
TArray<FGCS_TraceHandle> Handles;
|
||||
for (const FGameplayTag& Tag : TraceTags)
|
||||
{
|
||||
TArray<FGCS_TraceHandle> TagHandles;
|
||||
TagToHandles.MultiFind(Tag, TagHandles);
|
||||
for (const FGCS_TraceHandle& Handle : TagHandles)
|
||||
{
|
||||
if (!IsValid(SourceObject) || Handle.SourceObject == SourceObject)
|
||||
{
|
||||
Handles.AddUnique(Handle);
|
||||
}
|
||||
}
|
||||
}
|
||||
return Handles;
|
||||
}
|
||||
|
||||
TArray<FGCS_TraceHandle> UGCS_TraceSystemComponent::StartTraces(const FGameplayTagContainer& TraceTags, const UObject* SourceObject)
|
||||
{
|
||||
TArray<FGCS_TraceHandle> Handles = GetTraceHandlesByTagsAndSource(TraceTags, SourceObject);
|
||||
for (const FGCS_TraceHandle& Handle : Handles)
|
||||
{
|
||||
StartTrace(Handle);
|
||||
}
|
||||
return Handles;
|
||||
}
|
||||
|
||||
void UGCS_TraceSystemComponent::StartTraces(const TArray<FGCS_TraceHandle>& TraceHandles)
|
||||
{
|
||||
for (const FGCS_TraceHandle& Handle : TraceHandles)
|
||||
{
|
||||
StartTrace(Handle);
|
||||
}
|
||||
}
|
||||
|
||||
void UGCS_TraceSystemComponent::StartTrace(const FGCS_TraceHandle& TraceHandle)
|
||||
{
|
||||
if (TraceHandle.IsValidHandle())
|
||||
{
|
||||
if (const int32* StateIdx = HandleToStateIdx.Find(TraceHandle))
|
||||
{
|
||||
auto& State = GetWorld()->GetSubsystem<UGCS_TraceSubsystem>()->GetTraceStateAt(*StateIdx);
|
||||
GCS_CLOG(Verbose, "Started trace(%s).", *TraceHandle.ToDebugString())
|
||||
State.ChangeExecutionState(true);
|
||||
OnTraceStateChanged(TraceHandle, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UGCS_TraceSystemComponent::StopTraces(const TArray<FGCS_TraceHandle>& TraceHandles)
|
||||
{
|
||||
UGCS_TraceSubsystem* Subsystem = GetWorld()->GetSubsystem<UGCS_TraceSubsystem>();
|
||||
for (const FGCS_TraceHandle& TraceHandle : TraceHandles)
|
||||
{
|
||||
if (TraceHandle.IsValidHandle())
|
||||
{
|
||||
if (const int32* StateIdx = HandleToStateIdx.Find(TraceHandle))
|
||||
{
|
||||
auto& State = Subsystem->GetTraceStateAt(*StateIdx);
|
||||
GCS_CLOG(Verbose, "Stopped trace(%s).", *TraceHandle.ToDebugString())
|
||||
State.ChangeExecutionState(false);
|
||||
OnTraceStateChanged(TraceHandle, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UGCS_TraceSystemComponent::StopTrace(const FGCS_TraceHandle& TraceHandle)
|
||||
{
|
||||
if (TraceHandle.IsValidHandle())
|
||||
{
|
||||
if (const int32* StateIdx = HandleToStateIdx.Find(TraceHandle))
|
||||
{
|
||||
auto& State = GetWorld()->GetSubsystem<UGCS_TraceSubsystem>()->GetTraceStateAt(*StateIdx);
|
||||
State.ChangeExecutionState(false);
|
||||
OnTraceStateChanged(TraceHandle, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TArray<FGCS_TraceHandle> UGCS_TraceSystemComponent::AddTracesByDefinitions(const TArray<FGCS_TraceDefinition>& Definitions, UPrimitiveComponent* SourceComponent,
|
||||
UObject* SourceObject)
|
||||
{
|
||||
return AddTraces(Definitions, SourceComponent, SourceObject);
|
||||
}
|
||||
|
||||
TArray<FGCS_TraceHandle> UGCS_TraceSystemComponent::AddTracesByDefinitionHandles(const TArray<FDataTableRowHandle>& DefinitionHandles, UPrimitiveComponent* SourceComponent,
|
||||
UObject* SourceObject)
|
||||
{
|
||||
return AddTraces(DefinitionHandles, SourceComponent, SourceObject);
|
||||
}
|
||||
|
||||
FGCS_TraceHandle UGCS_TraceSystemComponent::AddTraceByDefinition(const FGCS_TraceDefinition& Definition, UPrimitiveComponent* SourceComponent, UObject* SourceObject)
|
||||
{
|
||||
return AddTrace(Definition, SourceComponent, SourceObject);
|
||||
}
|
||||
|
||||
TArray<FGCS_TraceHandle> UGCS_TraceSystemComponent::StartTracesByTagsAndSource(const FGameplayTagContainer& TraceTags, const UObject* SourceObject)
|
||||
{
|
||||
return StartTraces(TraceTags, SourceObject);
|
||||
}
|
||||
|
||||
void UGCS_TraceSystemComponent::StartTracesByHandles(const TArray<FGCS_TraceHandle>& TraceHandles)
|
||||
{
|
||||
return StartTraces(TraceHandles);
|
||||
}
|
||||
|
||||
void UGCS_TraceSystemComponent::StartTraceByHandle(const FGCS_TraceHandle& TraceHandle)
|
||||
{
|
||||
return StartTrace(TraceHandle);
|
||||
}
|
||||
|
||||
void UGCS_TraceSystemComponent::StopTracesByHandles(const TArray<FGCS_TraceHandle>& TraceHandles)
|
||||
{
|
||||
StopTraces(TraceHandles);
|
||||
}
|
||||
|
||||
void UGCS_TraceSystemComponent::StopTraceByHandle(const FGCS_TraceHandle& TraceHandle)
|
||||
{
|
||||
StopTrace(TraceHandle);
|
||||
}
|
||||
|
||||
void UGCS_TraceSystemComponent::RemoveTraceByHandle(const FGCS_TraceHandle& TraceHandle)
|
||||
{
|
||||
RemoveTrace(TraceHandle);
|
||||
}
|
||||
|
||||
TArray<FGCS_TraceHandle> UGCS_TraceSystemComponent::GetTraceHandlesByTag(FGameplayTag TraceToFind) const
|
||||
{
|
||||
TArray<FGCS_TraceHandle> Handles;
|
||||
TagToHandles.MultiFind(TraceToFind, Handles);
|
||||
return Handles;
|
||||
}
|
||||
|
||||
AActor* UGCS_TraceSystemComponent::GetTraceSourceActor(const FGCS_TraceHandle& TraceHandle) const
|
||||
{
|
||||
if (UPrimitiveComponent* SourceComponent = GetTraceSourceComponent(TraceHandle))
|
||||
{
|
||||
return SourceComponent->GetOwner();
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
UPrimitiveComponent* UGCS_TraceSystemComponent::GetTraceSourceComponent(const FGCS_TraceHandle& TraceHandle) const
|
||||
{
|
||||
if (TraceHandle.IsValidHandle())
|
||||
{
|
||||
if (const int32* StateIdx = HandleToStateIdx.Find(TraceHandle))
|
||||
{
|
||||
FGCS_TraceState& State = GetWorld()->GetSubsystem<UGCS_TraceSubsystem>()->GetTraceStateAt(*StateIdx);
|
||||
return State.SourceComponent;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void UGCS_TraceSystemComponent::RemoveAllTraces()
|
||||
{
|
||||
UWorld* World = GetWorld();
|
||||
if (!IsValid(World))
|
||||
{
|
||||
return;
|
||||
}
|
||||
UGCS_TraceSubsystem* Subsystem = World->GetSubsystem<UGCS_TraceSubsystem>();
|
||||
if (!IsValid(Subsystem))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// 新增:收集所有要移除的Idx和Guid,避免边遍历边修改
|
||||
TArray<TPair<int32, FGuid>> ToRemove;
|
||||
for (const TPair<FGCS_TraceHandle, int32>& Pair : HandleToStateIdx)
|
||||
{
|
||||
if (Pair.Key.IsValidHandle())
|
||||
{
|
||||
if (Subsystem->IsValidStateIdx(Pair.Value)) // 额外验证
|
||||
{
|
||||
const FGCS_TraceState& State = Subsystem->GetTraceStateAt(Pair.Value);
|
||||
ToRemove.Add(TPair<int32, FGuid>(Pair.Value, Pair.Key.Guid));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 先停止所有状态
|
||||
for (const auto& Pending : ToRemove)
|
||||
{
|
||||
if (Subsystem->IsValidStateIdx(Pending.Key))
|
||||
{
|
||||
auto& State = Subsystem->GetTraceStateAt(Pending.Key);
|
||||
State.ChangeExecutionState(false);
|
||||
}
|
||||
}
|
||||
|
||||
// 然后批量移除
|
||||
for (const auto& Pending : ToRemove)
|
||||
{
|
||||
Subsystem->RemoveTraceState(Pending.Key, Pending.Value);
|
||||
}
|
||||
|
||||
// 清空映射
|
||||
for (const TPair<FGCS_TraceHandle, int32>& Pair : HandleToStateIdx)
|
||||
{
|
||||
TagToHandles.RemoveSingle(Pair.Key.TraceTag, Pair.Key);
|
||||
}
|
||||
HandleToStateIdx.Empty();
|
||||
}
|
||||
|
||||
bool UGCS_TraceSystemComponent::IsTraceActive(const FGCS_TraceHandle& TraceHandle) const
|
||||
{
|
||||
if (const int32* StateIdx = HandleToStateIdx.Find(TraceHandle))
|
||||
{
|
||||
const FGCS_TraceState& State = GetWorld()->GetSubsystem<UGCS_TraceSubsystem>()->GetTraceStateAt(*StateIdx);
|
||||
return State.ExecutionState == EGCS_TraceExecutionState::InProgress;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void UGCS_TraceSystemComponent::OnTraceHitDetected(const FGCS_TraceHandle& TraceHandle, const TArray<FHitResult>& HitResults, const float DeltaTime, const uint32 TickIdx)
|
||||
{
|
||||
DECLARE_SCOPE_CYCLE_COUNTER(TEXT("UGCS_TraceSystemComponent::OnTraceHitDetected"), UGCS_TraceSystemComponent_OnTraceHitDetected, STATGROUP_GCS)
|
||||
|
||||
FScopeLock ScopedLock(&TraceDoneScopeLock);
|
||||
|
||||
for (const FHitResult& HitResult : HitResults)
|
||||
{
|
||||
// GCS_CLOG(Verbose, "Trace(%s) hit:%s at location(%s)", *TraceHandle.ToDebugString(), *GetNameSafe(HitResult.GetActor()), *HitResult.Location.ToString())
|
||||
GCS_CLOG(Verbose, "Trace(%s) hit:%s", *TraceHandle.ToDebugString(), *HitResult.ToString())
|
||||
OnTraceHitEvent.Broadcast(TraceHandle, HitResult);
|
||||
}
|
||||
}
|
||||
|
||||
void UGCS_TraceSystemComponent::OnTraceStateChanged(const FGCS_TraceHandle& TraceHandle, bool bNewState)
|
||||
{
|
||||
if (bNewState)
|
||||
{
|
||||
OnTraceStartedEvent.Broadcast(TraceHandle);
|
||||
}
|
||||
else
|
||||
{
|
||||
OnTraceStoppedEvent.Broadcast(TraceHandle);
|
||||
}
|
||||
OnTraceStateChangedEvent.Broadcast(TraceHandle, bNewState);
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
|
||||
#include "CombatFlow/GCS_AbilityActionSetSettings.h"
|
||||
|
||||
bool UGCS_AbilityActionSetSettings::SelectBestAbilityActions(const FGameplayTagContainer& SourceTags, const FGameplayTagContainer& TargetTags, const FGameplayTagContainer& AbilityTags,
|
||||
TArray<FGCS_AbilityAction>& Actions) const
|
||||
{
|
||||
FGCS_AbilityActionSet ActionSet;
|
||||
bool bFound = false;
|
||||
|
||||
for (int32 i = 0; i < ActionSets.Num(); i++)
|
||||
{
|
||||
if (ActionSets[i].AbilityTag.IsValid() && ActionSets[i].AbilityTag.MatchesAny(AbilityTags))
|
||||
{
|
||||
ActionSet = ActionSets[i];
|
||||
bFound = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!bFound)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// try finds in layers.
|
||||
|
||||
for (int32 i = 0; i < ActionSet.Layered.Num(); i++)
|
||||
{
|
||||
bool bMatchingSource = ActionSet.Layered[i].SourceTagQuery.IsEmpty() || ActionSet.Layered[i].SourceTagQuery.Matches(SourceTags);
|
||||
bool bMatchingTarget = ActionSet.Layered[i].TargetTagQuery.IsEmpty() || ActionSet.Layered[i].TargetTagQuery.Matches(TargetTags);
|
||||
if (bMatchingSource && bMatchingTarget)
|
||||
{
|
||||
Actions = ActionSet.Layered[i].Actions;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// falback to default.
|
||||
Actions = ActionSet.Actions;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#if WITH_EDITORONLY_DATA
|
||||
#include "UObject/ObjectSaveContext.h"
|
||||
|
||||
void UGCS_AbilityActionSetSettings::PreSave(FObjectPreSaveContext SaveContext)
|
||||
{
|
||||
Super::PreSave(SaveContext);
|
||||
|
||||
for (FGCS_AbilityActionSet& ActionSet : ActionSets)
|
||||
{
|
||||
for (FGCS_AbilityAction& Action : ActionSet.Actions)
|
||||
{
|
||||
Action.EditorFriendlyName = Action.Animation != nullptr ? Action.Animation.GetName() : TEXT("Empty Action");
|
||||
}
|
||||
for (FGCS_AbilityActionsWithQuery& Layered : ActionSet.Layered)
|
||||
{
|
||||
Layered.EditorFriendlyName = FString::Format(TEXT("Source:({0}) Target({1})"), {
|
||||
Layered.SourceTagQuery.IsEmpty() ? TEXT("Empty") : Layered.SourceTagQuery.GetDescription(),
|
||||
Layered.TargetTagQuery.IsEmpty() ? TEXT("Empty") : Layered.TargetTagQuery.GetDescription()
|
||||
});
|
||||
for (FGCS_AbilityAction& Action : Layered.Actions)
|
||||
{
|
||||
Action.EditorFriendlyName = Action.Animation != nullptr ? Action.Animation.GetName() : TEXT("Empty Action");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,4 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
|
||||
#include "CombatFlow/GCS_AttackDefinition.h"
|
||||
@@ -0,0 +1,189 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
|
||||
#include "CombatFlow/GCS_AttackRequest.h"
|
||||
#include "Engine/DataTable.h"
|
||||
#include "GCS_CombatEntityInterface.h"
|
||||
#include "GameFramework/Character.h"
|
||||
#include "Components/SkeletalMeshComponent.h"
|
||||
#include "Components/PrimitiveComponent.h"
|
||||
#include "GameFramework/PlayerController.h"
|
||||
#include "Utility/GCS_CombatFunctionLibrary.h"
|
||||
#include "Weapon/GCS_WeaponInterface.h"
|
||||
|
||||
FDataTableRowHandle UGCS_AttackRequest_Base::GetAttackDefinitionHandle_Implementation() const
|
||||
{
|
||||
return FDataTableRowHandle();
|
||||
}
|
||||
|
||||
FGCS_AttackDefinition UGCS_AttackRequest_Base::GetAttackDefinition() const
|
||||
{
|
||||
if (const FGCS_AttackDefinition* Def = GetAttackDefinitionHandle().GetRow<FGCS_AttackDefinition>(TEXT("Get Attack Definition from AttackRequest")))
|
||||
{
|
||||
return *Def;
|
||||
}
|
||||
return FGCS_AttackDefinition();
|
||||
}
|
||||
|
||||
FDataTableRowHandle UGCS_AttackRequest_Melee::GetAttackDefinitionHandle_Implementation() const
|
||||
{
|
||||
return AttackDefinitionHandle;
|
||||
}
|
||||
|
||||
FGCS_BulletDefinition UGCS_AttackRequest_Bullet::GetBulletDefinition() const
|
||||
{
|
||||
if (auto Def = BulletDefinitionHandle.GetRow<FGCS_BulletDefinition>(TEXT("Get Bullet Definition from AttackRequest_Bullet")))
|
||||
{
|
||||
return *Def;
|
||||
}
|
||||
return FGCS_BulletDefinition();
|
||||
}
|
||||
|
||||
FDataTableRowHandle UGCS_AttackRequest_Bullet::GetAttackDefinitionHandle_Implementation() const
|
||||
{
|
||||
return GetBulletDefinition().AttackDefinition;
|
||||
}
|
||||
|
||||
FVector UGCS_AttackRequest_Bullet::GetPawnTargetingSourceLocation_Implementation(APawn* SourcePawn) const
|
||||
{
|
||||
check(SourcePawn);
|
||||
|
||||
FVector RetLocation = SourcePawn->GetActorLocation();
|
||||
if (SourceSocketName != NAME_None)
|
||||
{
|
||||
if (UMeshComponent* Mesh = UGCS_CombatFunctionLibrary::GetMainMeshComponent(SourcePawn))
|
||||
{
|
||||
if (Mesh->DoesSocketExist(SourceSocketName))
|
||||
{
|
||||
RetLocation = Mesh->GetSocketLocation(SourceSocketName);
|
||||
}
|
||||
}
|
||||
if (ACharacter* Char = Cast<ACharacter>(SourcePawn))
|
||||
{
|
||||
if (Char->GetMesh() && Char->GetMesh()->DoesSocketExist(SourceSocketName))
|
||||
{
|
||||
RetLocation = Char->GetMesh()->GetSocketLocation(SourceSocketName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FVector LocalOffsetInWorld = SourcePawn->GetActorTransform().TransformVector(LocationOffset);
|
||||
return RetLocation + LocalOffsetInWorld;
|
||||
}
|
||||
|
||||
|
||||
FVector UGCS_AttackRequest_Bullet::GetWeaponTargetingSourceLocation_Implementation(APawn* SourcePawn) const
|
||||
{
|
||||
check(SourcePawn)
|
||||
|
||||
FVector RetLocation = SourcePawn->GetActorLocation();
|
||||
|
||||
if (UObject* CombatImplementer = UGCS_CombatFunctionLibrary::GetCombatEntity(SourcePawn))
|
||||
{
|
||||
if (UObject* WeaponImplementer = IGCS_CombatEntityInterface::Execute_GetCurrentWeapon(CombatImplementer, nullptr))
|
||||
{
|
||||
if (UPrimitiveComponent* PrimitiveComponent = IGCS_WeaponInterface::Execute_GetPrimitiveComponent(WeaponImplementer))
|
||||
{
|
||||
if (SourceWeaponSocketName != NAME_None && PrimitiveComponent->DoesSocketExist(SourceWeaponSocketName))
|
||||
{
|
||||
RetLocation = PrimitiveComponent->GetSocketLocation(SourceWeaponSocketName);
|
||||
}
|
||||
else
|
||||
{
|
||||
RetLocation = PrimitiveComponent->GetOwner()->GetActorLocation();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FVector LocalOffsetInWorld = SourcePawn->GetActorTransform().TransformVector(LocationOffset);
|
||||
return RetLocation + LocalOffsetInWorld;
|
||||
}
|
||||
|
||||
FTransform UGCS_AttackRequest_Bullet::GetTargetingTransform_Implementation(APawn* SourcePawn, EGCS_AbilityTargetingSourceType Source) const
|
||||
{
|
||||
check(SourcePawn);
|
||||
|
||||
// The caller should determine the transform without calling this if the mode is custom!
|
||||
check(Source != EGCS_AbilityTargetingSourceType::Custom);
|
||||
|
||||
|
||||
static double FocalDistance = 1024.0f;
|
||||
FVector FocalLoc;
|
||||
|
||||
bool bFocalBased = SourcePawn->Controller != nullptr && (Source == EGCS_AbilityTargetingSourceType::CameraTowardsFocus || Source == EGCS_AbilityTargetingSourceType::PawnTowardsFocus || Source ==
|
||||
EGCS_AbilityTargetingSourceType::WeaponTowardsFocus);
|
||||
|
||||
if (bFocalBased)
|
||||
{
|
||||
APlayerController* PC = Cast<APlayerController>(SourcePawn->Controller);
|
||||
FVector CamLoc;
|
||||
FRotator CamRot;
|
||||
|
||||
if (PC != nullptr)
|
||||
{
|
||||
PC->GetPlayerViewPoint(CamLoc, CamRot);
|
||||
}
|
||||
else
|
||||
{
|
||||
CamLoc = SourceSocketName != NAME_None ? GetPawnTargetingSourceLocation(SourcePawn) : SourcePawn->GetActorLocation() + FVector(0, 0, SourcePawn->BaseEyeHeight);
|
||||
CamRot = SourcePawn->Controller->GetControlRotation();
|
||||
}
|
||||
|
||||
// Determine initial focal point to
|
||||
FVector AimDir = CamRot.Vector().GetSafeNormal();
|
||||
FocalLoc = CamLoc + (AimDir * FocalDistance);
|
||||
|
||||
// Move the start and focal point up in front of pawn
|
||||
// if (PC)
|
||||
// {
|
||||
// const FVector WeaponLoc = GetWeaponTargetingSourceLocation(SourcePawn);
|
||||
// CamLoc = FocalLoc + (((WeaponLoc - FocalLoc) | AimDir) * AimDir);
|
||||
// FocalLoc = CamLoc + (AimDir * FocalDistance);
|
||||
// }
|
||||
|
||||
// valid camera and want camera's location
|
||||
if (Source == EGCS_AbilityTargetingSourceType::CameraTowardsFocus)
|
||||
{
|
||||
// If we're camera -> focus then we're done
|
||||
return FTransform(CamRot, CamLoc);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// valid camera nd want pawn's location.
|
||||
if (bFocalBased && Source == EGCS_AbilityTargetingSourceType::PawnTowardsFocus)
|
||||
{
|
||||
FVector SourceLoc = GetPawnTargetingSourceLocation(SourcePawn);
|
||||
|
||||
// Return a rotator pointing at the focal point from the source
|
||||
return FTransform((FocalLoc - SourceLoc).Rotation(), SourceLoc);
|
||||
}
|
||||
|
||||
// valid camera and want weapon's location.
|
||||
if (bFocalBased && Source == EGCS_AbilityTargetingSourceType::WeaponTowardsFocus)
|
||||
{
|
||||
FVector SourceLoc = GetWeaponTargetingSourceLocation(SourcePawn);
|
||||
|
||||
// Return a rotator pointing at the focal point from the source
|
||||
return FTransform((FocalLoc - SourceLoc).Rotation(), SourceLoc);
|
||||
}
|
||||
|
||||
// Either we want the weapon's location, or we failed to find a camera
|
||||
if (Source == EGCS_AbilityTargetingSourceType::WeaponForward || Source == EGCS_AbilityTargetingSourceType::WeaponTowardsFocus)
|
||||
{
|
||||
FVector SourceLoc = GetWeaponTargetingSourceLocation(SourcePawn);
|
||||
return FTransform(SourcePawn->GetActorQuat(), SourceLoc);
|
||||
}
|
||||
|
||||
// Either we want the pawn's location, or we failed to find a camera
|
||||
if (Source == EGCS_AbilityTargetingSourceType::PawnForward || Source == EGCS_AbilityTargetingSourceType::PawnTowardsFocus)
|
||||
{
|
||||
// Either we want the pawn's location, or we failed to find a camera
|
||||
FVector SourceLoc = GetPawnTargetingSourceLocation(SourcePawn);
|
||||
return FTransform(SourcePawn->GetActorQuat(), SourceLoc);
|
||||
}
|
||||
|
||||
// If we got here, either we don't have a camera or we don't want to use it, either way go forward
|
||||
return FTransform(SourcePawn->GetActorQuat(), SourcePawn->GetActorLocation());
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
|
||||
#include "CombatFlow/GCS_AttackResult.h"
|
||||
|
||||
#include "GameplayEffect.h"
|
||||
#include "GCS_LogChannels.h"
|
||||
#include "GCS_CombatSystemComponent.h"
|
||||
#include "CombatFlow/GCS_CombatFlow.h"
|
||||
#include "Utility/GCS_CombatFunctionLibrary.h"
|
||||
|
||||
|
||||
void FGCS_AttackResult::PostReplicatedAdd(const struct FGCS_AttackResultContainer& InArray)
|
||||
{
|
||||
}
|
||||
|
||||
FGCS_AttackResultContainer::FGCS_AttackResultContainer(): CombatFlow(nullptr), CombatSystemComponent(nullptr), MaxSize(5)
|
||||
{
|
||||
}
|
||||
|
||||
FGCS_AttackResultContainer::FGCS_AttackResultContainer(UGCS_CombatFlow* InCombatFlow, int32 InMaxSize): CombatFlow(InCombatFlow), CombatSystemComponent(nullptr), MaxSize(InMaxSize)
|
||||
{
|
||||
}
|
||||
|
||||
FGCS_AttackResultContainer::FGCS_AttackResultContainer(UGCS_CombatSystemComponent* InCombatSystemComponent, int32 InMaxSize): CombatFlow(nullptr), CombatSystemComponent(InCombatSystemComponent),
|
||||
MaxSize(InMaxSize)
|
||||
{
|
||||
}
|
||||
|
||||
void FGCS_AttackResultContainer::AddEntry(FGCS_AttackResult& NewEntry)
|
||||
{
|
||||
if (Results.Num() >= 5)
|
||||
{
|
||||
Results.RemoveAtSwap(0);
|
||||
MarkArrayDirty();
|
||||
}
|
||||
|
||||
Results.Add(NewEntry);
|
||||
check(CombatSystemComponent != nullptr && CombatSystemComponent->GetOwner());
|
||||
|
||||
if (CombatSystemComponent->GetCombatFlow())
|
||||
{
|
||||
CombatSystemComponent->GetCombatFlow()->HandleAttackResult(NewEntry);
|
||||
NewEntry.bConsumed = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
UE_LOG(LogGCS, Error, TEXT("Missing Combat Flow on %s's combat system component."), *CombatSystemComponent->GetOwner()->GetName());
|
||||
}
|
||||
|
||||
MarkItemDirty(NewEntry);
|
||||
}
|
||||
|
||||
void FGCS_AttackResultContainer::PostReplicatedAdd(const TArrayView<int32> AddedIndices, int32 FinalSize)
|
||||
{
|
||||
for (int32 Index : AddedIndices)
|
||||
{
|
||||
FGCS_AttackResult& Entry = Results[Index];
|
||||
|
||||
FGCS_ContextPayload_Combat* CombatPayload = UGCS_CombatFunctionLibrary::EffectContextGetMutableCombatPayload(Results[Index].EffectContextHandle);
|
||||
if (CombatPayload != nullptr && CombatPayload->PredictionKey.IsLocalClientKey())
|
||||
{
|
||||
checkf(!CombatPayload->bIsPredictingContext, TEXT("PredictingContext never should hit this!"))
|
||||
// PredictionKey will only be valid on the client that predicted it. So if this has a valid PredictionKey, we can assume we already predicted it and shouldn't handle attack results.
|
||||
if (HasPredictedResultWithPredictedKey(CombatPayload->PredictionKey))
|
||||
{
|
||||
GCS_LOG(Verbose, "Found already predicted attack result!")
|
||||
Entry.bWasPredicated = true;
|
||||
}
|
||||
}
|
||||
Entry.bWasReplicated = true;
|
||||
|
||||
CombatSystemComponent->GetCombatFlow()->HandleAttackResult(Entry);
|
||||
|
||||
Entry.bConsumed = true;
|
||||
}
|
||||
}
|
||||
|
||||
void FGCS_AttackResultContainer::PostReplicatedChange(const TArrayView<int32>& ChangedIndices, int32 FinalSize)
|
||||
{
|
||||
for (int32 Index : ChangedIndices)
|
||||
{
|
||||
if (!Results[Index].bConsumed)
|
||||
{
|
||||
CombatSystemComponent->GetCombatFlow()->HandleAttackResult(Results[Index]);
|
||||
Results[Index].bConsumed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool FGCS_AttackResultContainer::HasPredictedResultWithPredictedKey(FPredictionKey PredictionKey) const
|
||||
{
|
||||
for (const FGCS_AttackResult& Result : Results)
|
||||
{
|
||||
if (Result.bConsumed)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (const FGCS_ContextPayload_Combat* CombatPayload = UGCS_CombatFunctionLibrary::EffectContextGetMutableCombatPayload(Result.EffectContextHandle))
|
||||
{
|
||||
if (CombatPayload->PredictionKey.IsValidKey() && CombatPayload->PredictionKey == PredictionKey && CombatPayload->PredictionKey.WasReceived() == false)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
@@ -0,0 +1,242 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
|
||||
#include "CombatFlow/GCS_AttackResultProcessor.h"
|
||||
#include "AbilitySystemBlueprintLibrary.h"
|
||||
#include "AbilitySystemComponent.h"
|
||||
#include "AbilitySystemGlobals.h"
|
||||
#include "GameplayCueFunctionLibrary.h"
|
||||
#include "GameFramework/Pawn.h"
|
||||
#include "AbilitySystem/GCS_GameplayEffectContext.h"
|
||||
#include "CombatFlow/GCS_CombatFlow.h"
|
||||
#include "UObject/ObjectSaveContext.h"
|
||||
#include "Utilities/GGA_GameplayCueFunctionLibrary.h"
|
||||
#include "Utility/GCS_CombatFunctionLibrary.h"
|
||||
|
||||
bool UGCS_AttackResultProcessor::ProcessIncomingAttackResult(const FGCS_AttackResult& AttackResult)
|
||||
{
|
||||
if (!AttackResult.bConsumed)
|
||||
{
|
||||
HandleIncomingAttackResult(AttackResult);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
EGCS_AttackResultProcessorPolicy UGCS_AttackResultProcessor::GetExecutePolicy_Implementation() const
|
||||
{
|
||||
return ExecutePolicy;
|
||||
}
|
||||
|
||||
class UWorld* UGCS_AttackResultProcessor::GetWorld() const
|
||||
{
|
||||
if (AActor* OwningActor = GetOwningActor())
|
||||
{
|
||||
return OwningActor->GetWorld();
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
AActor* UGCS_AttackResultProcessor::GetOwningActor() const
|
||||
{
|
||||
if (UGCS_CombatFlow* Flow = Cast<UGCS_CombatFlow>(GetOuter()))
|
||||
{
|
||||
return Flow->GetFlowOwner();
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
UAbilitySystemComponent* UGCS_AttackResultProcessor::GetOwningAbilitySystemComponent() const
|
||||
{
|
||||
if (AActor* OwningActor = GetOwningActor())
|
||||
{
|
||||
return UAbilitySystemGlobals::GetAbilitySystemComponentFromActor(OwningActor);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
FString UGCS_AttackResultProcessor::GetEditorFriendlyName_Implementation() const
|
||||
{
|
||||
return TEXT("");
|
||||
}
|
||||
|
||||
void UGCS_AttackResultProcessor::HandleIncomingAttackResult_Implementation(const FGCS_AttackResult& AttackResult) const
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
#if WITH_EDITORONLY_DATA
|
||||
void UGCS_AttackResultProcessor::PreSave(FObjectPreSaveContext SaveContext)
|
||||
{
|
||||
EditorFriendlyName = GetEditorFriendlyName();
|
||||
Super::PreSave(SaveContext);
|
||||
}
|
||||
#endif
|
||||
|
||||
bool UGCS_AttackResultProcessor_WithTagRequirement::ProcessIncomingAttackResult(const FGCS_AttackResult& AttackResult)
|
||||
{
|
||||
if (!AttackResult.bConsumed)
|
||||
{
|
||||
bool bMatchSourceQuery = SourceTagQuery.IsEmpty() || SourceTagQuery.Matches(AttackResult.AggregatedSourceTags);
|
||||
bool bMatchTargetQuery = TargetTagQuery.IsEmpty() || TargetTagQuery.Matches(AttackResult.AggregatedTargetTags);
|
||||
|
||||
if (bMatchSourceQuery && bMatchTargetQuery)
|
||||
{
|
||||
HandleIncomingAttackResult(AttackResult);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
FString UGCS_AttackResultProcessor_WithTagRequirement::GetSourceTagQueryDesc() const
|
||||
{
|
||||
return SourceTagQuery.GetDescription();
|
||||
}
|
||||
|
||||
FString UGCS_AttackResultProcessor_WithTagRequirement::GetTargetTagQueryDesc() const
|
||||
{
|
||||
return TargetTagQuery.GetDescription();
|
||||
}
|
||||
|
||||
void UGCS_AttackResultProcessor_Death::HandleIncomingAttackResult_Implementation(const FGCS_AttackResult& AttackResult) const
|
||||
{
|
||||
UAbilitySystemComponent* ASC = GetOwningAbilitySystemComponent();
|
||||
if (ASC)
|
||||
{
|
||||
if (true)
|
||||
{
|
||||
}
|
||||
}
|
||||
Super::HandleIncomingAttackResult_Implementation(AttackResult);
|
||||
}
|
||||
|
||||
void UGCS_AttackResultProcessor_GameplayEvent::HandleIncomingAttackResult_Implementation(const FGCS_AttackResult& AttackResult) const
|
||||
{
|
||||
APawn* OwningPawn = Cast<APawn>(GetOwningActor());
|
||||
if (OwningPawn == nullptr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (OwningPawn->HasAuthority() || OwningPawn->IsLocallyControlled())
|
||||
{
|
||||
UAbilitySystemComponent* ASC = bSendToAttacker ? AttackResult.EffectContextHandle.GetInstigatorAbilitySystemComponent() : GetOwningAbilitySystemComponent();
|
||||
if (IsValid(ASC))
|
||||
{
|
||||
FGameplayEventData Payload = FGameplayEventData();
|
||||
|
||||
FGameplayTagContainer InstigatorTags = AttackResult.AggregatedSourceTags;
|
||||
if (const FGCS_ContextPayload_Combat* CombatPayload = UGCS_CombatFunctionLibrary::EffectContextGetMutableCombatPayload(AttackResult.EffectContextHandle))
|
||||
{
|
||||
InstigatorTags.AppendTags(CombatPayload->DynamicTags);
|
||||
}
|
||||
|
||||
Payload.Instigator = AttackResult.EffectContextHandle.GetInstigator();
|
||||
// Payload.OptionalObject = AttackResult.OptionalObject;
|
||||
Payload.Target = OwningPawn;
|
||||
Payload.ContextHandle = AttackResult.EffectContextHandle;
|
||||
Payload.InstigatorTags = InstigatorTags;
|
||||
Payload.TargetTags = AttackResult.AggregatedTargetTags;
|
||||
|
||||
for (const FGameplayTag& Tag : EventTriggers)
|
||||
{
|
||||
// UAbilitySystemBlueprintLibrary::SendGameplayEventToActor(OwningPawn, Tag, Payload);
|
||||
Payload.EventTag = Tag;
|
||||
// FScopedPredictionWindow NewScopedWindow(ASC, true);
|
||||
ASC->HandleGameplayEvent(Tag, &Payload);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FString UGCS_AttackResultProcessor_GameplayEvent::GetEditorFriendlyName_Implementation() const
|
||||
{
|
||||
FString Result;
|
||||
|
||||
for (FGameplayTag EventTrigger : EventTriggers)
|
||||
{
|
||||
if (EventTrigger.IsValid())
|
||||
{
|
||||
TArray<FString> Parts;
|
||||
EventTrigger.GetTagName().ToString().ParseIntoArray(Parts,TEXT("."));
|
||||
if (Parts.Num() > 0)
|
||||
{
|
||||
Result.Append(FString::Format(TEXT(" ({0}) "), {Parts.Last()}));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return FString::Format(TEXT("Send Event:{0}"), {Result});
|
||||
}
|
||||
|
||||
void UGCS_AttackResultProcessor_GameplayCue::HandleIncomingAttackResult_Implementation(const FGCS_AttackResult& AttackResult) const
|
||||
{
|
||||
AActor* OwningActor = GetOwningActor();
|
||||
if (!OwningActor)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
TArray<FGameplayTag> CuesToTrigger = GameplayCues;
|
||||
|
||||
FGCS_AttackDefinition Atk = UGCS_CombatFunctionLibrary::EffectContextGetAttackDefinition(AttackResult.EffectContextHandle);
|
||||
|
||||
if (!Atk.TargetGameplayCues.IsEmpty())
|
||||
{
|
||||
CuesToTrigger = Atk.TargetGameplayCues;
|
||||
}
|
||||
|
||||
FGameplayCueParameters Params = FGameplayCueParameters();
|
||||
if (const FHitResult* HitResult = AttackResult.EffectContextHandle.GetHitResult())
|
||||
{
|
||||
if (HitResult->GetActor() == OwningActor)
|
||||
{
|
||||
Params = UGameplayCueFunctionLibrary::MakeGameplayCueParametersFromHitResult(*HitResult);
|
||||
}
|
||||
}
|
||||
|
||||
FGameplayTagContainer InstigatorTags = AttackResult.AggregatedSourceTags;
|
||||
if (const FGCS_ContextPayload_Combat* CombatPayload = UGCS_CombatFunctionLibrary::EffectContextGetMutableCombatPayload(AttackResult.EffectContextHandle))
|
||||
{
|
||||
InstigatorTags.AppendTags(CombatPayload->DynamicTags);
|
||||
Params.RawMagnitude = CombatPayload->GetTaggedValue(RawMagnitudeTag);
|
||||
Params.NormalizedMagnitude = CombatPayload->GetTaggedValue(NormalizedMagnitudeTag);
|
||||
}
|
||||
|
||||
Params.AggregatedSourceTags = InstigatorTags;
|
||||
Params.AggregatedTargetTags = AttackResult.AggregatedTargetTags;
|
||||
Params.EffectCauser = AttackResult.EffectContextHandle.GetEffectCauser();
|
||||
Params.Instigator = AttackResult.EffectContextHandle.GetInstigator();
|
||||
|
||||
Params.EffectContext = AttackResult.EffectContextHandle;
|
||||
|
||||
ModifyGameplayCueParametersBeforeExecute(Params);
|
||||
|
||||
for (const FGameplayTag& Cue : CuesToTrigger)
|
||||
{
|
||||
if (!Cue.IsValid())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
UGGA_GameplayCueFunctionLibrary::ExecuteGameplayCueLocal(OwningActor, Cue, Params);
|
||||
}
|
||||
}
|
||||
|
||||
FString UGCS_AttackResultProcessor_GameplayCue::GetEditorFriendlyName_Implementation() const
|
||||
{
|
||||
FString Result;
|
||||
for (FGameplayTag EventTrigger : GameplayCues)
|
||||
{
|
||||
if (EventTrigger.IsValid())
|
||||
{
|
||||
TArray<FString> Parts;
|
||||
EventTrigger.GetTagName().ToString().ParseIntoArray(Parts,TEXT("."));
|
||||
if (Parts.Num() > 0)
|
||||
{
|
||||
Result.Append(FString::Format(TEXT(" ({0}) "), {Parts.Last()}));
|
||||
}
|
||||
}
|
||||
}
|
||||
return FString::Format(TEXT("Execute cues:{0}"), {Result});
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
|
||||
#include "CombatFlow/GCS_CombatFlow.h"
|
||||
#include "AbilitySystemBlueprintLibrary.h"
|
||||
#include "GameFramework/Pawn.h"
|
||||
#include "Utilities/GGA_AbilitySystemFunctionLibrary.h"
|
||||
#include "GCS_CombatSystemComponent.h"
|
||||
#include "GCS_LogChannels.h"
|
||||
#include "CombatFlow/GCS_AttackResultProcessor.h"
|
||||
#include "Utility/GCS_CombatFunctionLibrary.h"
|
||||
|
||||
|
||||
UGCS_CombatFlow::UGCS_CombatFlow()
|
||||
{
|
||||
Owner = nullptr;
|
||||
}
|
||||
|
||||
void UGCS_CombatFlow::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
|
||||
{
|
||||
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
|
||||
}
|
||||
|
||||
void UGCS_CombatFlow::Initialize(AActor* NewOwner)
|
||||
{
|
||||
Owner = NewOwner;
|
||||
// ProcessedAttacks.SetCombatFlow(this);
|
||||
CombatComponent = UGCS_CombatSystemComponent::GetCombatSystemComponent(Owner);
|
||||
}
|
||||
|
||||
void UGCS_CombatFlow::HandlePreGameplayEffectSpecApply_Implementation(const FGameplayEffectSpec& Spec, UAbilitySystemComponent* AbilitySystemComponent,
|
||||
FGameplayTagContainer& OutDynamicTagsAppendToSpec)
|
||||
{
|
||||
}
|
||||
|
||||
void UGCS_CombatFlow::HandleGameplayEffectExecute_Implementation(const FGGA_GameplayEffectModCallbackData& Payload)
|
||||
{
|
||||
}
|
||||
|
||||
void UGCS_CombatFlow::HandleAttackResult_Implementation(const FGCS_AttackResult& InPayload)
|
||||
{
|
||||
FGCS_AttackResult ModifiedPayload = InPayload;
|
||||
bool bPredictingContext = UGCS_CombatFunctionLibrary::EffectContextGetIsPredictingContext(InPayload.EffectContextHandle);
|
||||
const FGCS_ContextPayload_Combat* CombatPayload = UGCS_CombatFunctionLibrary::EffectContextGetMutableCombatPayload(ModifiedPayload.EffectContextHandle);
|
||||
|
||||
if (CombatPayload)
|
||||
{
|
||||
ModifiedPayload.AggregatedSourceTags.AppendTags(CombatPayload->DynamicTags);
|
||||
}
|
||||
|
||||
CombatComponent->SetLastProcessedAttackResult(ModifiedPayload);
|
||||
|
||||
for (int32 i = 0; i < AttackResultProcessors.Num(); i++)
|
||||
{
|
||||
auto& Processor = AttackResultProcessors[i];
|
||||
if (!IsValid(Processor))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
bool bShouldExecute = Processor->GetExecutePolicy() == EGCS_AttackResultProcessorPolicy::Default && !bPredictingContext;
|
||||
|
||||
if (Processor->GetExecutePolicy() == EGCS_AttackResultProcessorPolicy::LocalPredicted)
|
||||
{
|
||||
bShouldExecute = bPredictingContext || !InPayload.bWasPredicated;
|
||||
if (!bShouldExecute)
|
||||
{
|
||||
GCS_OWNED_CLOG(GetFlowOwner(), VeryVerbose, "Skipped local predicted processor at idx(%d) of type(%s)", i, *Processor->GetClass()->GetName())
|
||||
}
|
||||
}
|
||||
|
||||
if (Processor->GetExecutePolicy() == EGCS_AttackResultProcessorPolicy::ServerOnly)
|
||||
{
|
||||
bShouldExecute = !bPredictingContext && !InPayload.bWasReplicated;
|
||||
if (!bShouldExecute)
|
||||
{
|
||||
GCS_OWNED_CLOG(GetFlowOwner(), VeryVerbose, "Skipped server only processor at idx(%d) of type(%s)", i, *Processor->GetClass()->GetName())
|
||||
}
|
||||
}
|
||||
|
||||
if (bShouldExecute)
|
||||
{
|
||||
#if WITH_EDITOR
|
||||
// Debugging says not enabled.
|
||||
if (!Processor->GetEditorEnableState())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
#endif
|
||||
if (Processor->ProcessIncomingAttackResult(ModifiedPayload))
|
||||
{
|
||||
GCS_OWNED_CLOG(GetFlowOwner(), VeryVerbose, "%sExecuted processor at idx(%d) of type(%s)", bPredictingContext?TEXT("Predicate "):TEXT(""), i, *Processor->GetClass()->GetName())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
|
||||
#include "Combo/GCS_ComboDefinition.h"
|
||||
@@ -0,0 +1,20 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
|
||||
#include "GCS_ActorOwnedObject.h"
|
||||
#include "GameFramework/Actor.h"
|
||||
|
||||
UWorld* UGCS_ActorOwnedObject::GetWorld() const
|
||||
{
|
||||
// To Make sure the outer is Valid and can be used
|
||||
if (!HasAnyFlags(RF_ClassDefaultObject) && !GetOuter()->HasAnyFlags(RF_BeginDestroyed) && !GetOuter()->IsUnreachable())
|
||||
{
|
||||
//Attempt to get the world
|
||||
AActor* Outer = GetTypedOuter<AActor>();
|
||||
if (Outer != nullptr)
|
||||
{
|
||||
return Outer->GetWorld();
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
|
||||
#include "GCS_CombatEntityInterface.h"
|
||||
@@ -0,0 +1,4 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
|
||||
#include "GCS_CombatEnumLibrary.h"
|
||||
@@ -0,0 +1,6 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
|
||||
#include "GCS_CombatStructLibrary.h"
|
||||
#include "Animation/AnimMontage.h"
|
||||
#include "Collision/DEPRECATED_GCS_CollisionTraceInstance.h"
|
||||
@@ -0,0 +1,318 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
|
||||
#include "GCS_CombatSystemComponent.h"
|
||||
#include "AbilitySystemBlueprintLibrary.h"
|
||||
#include "TimerManager.h"
|
||||
#include "GCS_LogChannels.h"
|
||||
#include "Engine/World.h"
|
||||
#include "GameFramework/Controller.h"
|
||||
#include "Components/SkeletalMeshComponent.h"
|
||||
#include "CombatFlow/GCS_AttackRequest.h"
|
||||
#include "CombatFlow/GCS_CombatFlow.h"
|
||||
#include "GameFramework/GameStateBase.h"
|
||||
#include "Net/UnrealNetwork.h"
|
||||
#include "Animation/AnimMontage.h"
|
||||
#include "Animation/AnimInstance.h"
|
||||
#include "Utility/GCS_CombatFunctionLibrary.h"
|
||||
|
||||
|
||||
UGCS_CombatSystemComponent::UGCS_CombatSystemComponent() : AttackResultContainer(this, 10)
|
||||
{
|
||||
SetIsReplicatedByDefault(true);
|
||||
bWantsInitializeComponent = true;
|
||||
bReplicateUsingRegisteredSubObjectList = true;
|
||||
}
|
||||
|
||||
void UGCS_CombatSystemComponent::InitializeComponent()
|
||||
{
|
||||
AttackResultContainer.SetOwningCombatSystem(this);
|
||||
Super::InitializeComponent();
|
||||
}
|
||||
|
||||
void UGCS_CombatSystemComponent::BeginPlay()
|
||||
{
|
||||
if (GetWorld()->IsGameWorld())
|
||||
{
|
||||
if (GetOwner()->HasAuthority() && CombatFlowClass)
|
||||
{
|
||||
UGCS_CombatFlow* LocalNewProperty = NewObject<UGCS_CombatFlow>(GetOwner(), CombatFlowClass);
|
||||
LocalNewProperty->Initialize(GetOwner());
|
||||
CombatFlow = LocalNewProperty;
|
||||
AddReplicatedSubObject(CombatFlow);
|
||||
}
|
||||
UGGA_AbilitySystemGlobals::RegisterEventReceiver(this);
|
||||
}
|
||||
Super::BeginPlay();
|
||||
}
|
||||
|
||||
void UGCS_CombatSystemComponent::EndPlay(const EEndPlayReason::Type EndPlayReason)
|
||||
{
|
||||
Super::EndPlay(EndPlayReason);
|
||||
UGGA_AbilitySystemGlobals::UnregisterEventReceiver(this);
|
||||
if (GetOwner() && GetOwner()->HasAuthority())
|
||||
{
|
||||
if (CombatFlow)
|
||||
{
|
||||
RemoveReplicatedSubObject(CombatFlow);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UGCS_CombatSystemComponent::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
|
||||
{
|
||||
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
|
||||
|
||||
DOREPLIFETIME(ThisClass, CombatFlow);
|
||||
DOREPLIFETIME(ThisClass, AttackResultContainer);
|
||||
DOREPLIFETIME(ThisClass, ReplicatedMontageInfo);
|
||||
|
||||
FDoRepLifetimeParams Parameters;
|
||||
Parameters.bIsPushBased = true;
|
||||
|
||||
Parameters.Condition = COND_SkipOwner;
|
||||
|
||||
DOREPLIFETIME_WITH_PARAMS_FAST(ThisClass, ComboStep, Parameters)
|
||||
}
|
||||
|
||||
UGCS_CombatSystemComponent* UGCS_CombatSystemComponent::GetCombatSystemComponent(const AActor* Actor)
|
||||
{
|
||||
return Actor ? Actor->FindComponentByClass<UGCS_CombatSystemComponent>() : nullptr;
|
||||
}
|
||||
|
||||
bool UGCS_CombatSystemComponent::FindCombatSystemComponent(const AActor* Actor, UGCS_CombatSystemComponent*& CombatComponent)
|
||||
{
|
||||
CombatComponent = Actor ? Actor->FindComponentByClass<UGCS_CombatSystemComponent>() : nullptr;
|
||||
return IsValid(CombatComponent);
|
||||
}
|
||||
|
||||
bool UGCS_CombatSystemComponent::FindTypedCombatSystemComponent(AActor* Actor, TSubclassOf<UGCS_CombatSystemComponent> DesiredClass, UGCS_CombatSystemComponent*& Component)
|
||||
{
|
||||
if (UGCS_CombatSystemComponent* Instance = GetCombatSystemComponent(Actor))
|
||||
{
|
||||
if (Instance->GetClass()->IsChildOf(DesiredClass))
|
||||
{
|
||||
Component = Instance;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
UGCS_CombatFlow* UGCS_CombatSystemComponent::GetCombatFlow() const
|
||||
{
|
||||
return CombatFlow;
|
||||
}
|
||||
|
||||
void UGCS_CombatSystemComponent::RegisterAttackResult(FGCS_AttackResult& Payload)
|
||||
{
|
||||
//TODO Should make this server only?
|
||||
if (GetOwner() && GetOwner()->HasAuthority())
|
||||
{
|
||||
}
|
||||
AttackResultContainer.AddEntry(Payload);
|
||||
}
|
||||
|
||||
FGCS_AttackResult UGCS_CombatSystemComponent::GetLastProcessedAttackResult() const
|
||||
{
|
||||
return LastProcessedAttackResult;
|
||||
}
|
||||
|
||||
void UGCS_CombatSystemComponent::SetLastProcessedAttackResult(const FGCS_AttackResult& Payload)
|
||||
{
|
||||
LastProcessedAttackResult = Payload;
|
||||
}
|
||||
|
||||
void UGCS_CombatSystemComponent::PlayPredictableMontageForTarget(UGCS_CombatSystemComponent* TargetCSC, FGCS_PlayMontageRequest Request)
|
||||
{
|
||||
if (GetOwnerRole() >= ROLE_Authority)
|
||||
{
|
||||
TargetCSC->SetReplicatedMontage(Request);
|
||||
}
|
||||
|
||||
if (GetOwnerRole() == ROLE_AutonomousProxy)
|
||||
{
|
||||
TargetCSC->PlayPredictedMontage(Request);
|
||||
ServerPlayPredictableMontageForTarget(TargetCSC, Request);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void UGCS_CombatSystemComponent::ServerPlayPredictableMontageForTarget_Implementation(UGCS_CombatSystemComponent* TargetCSC, FGCS_PlayMontageRequest Request)
|
||||
{
|
||||
TargetCSC->SetReplicatedMontage(Request);
|
||||
}
|
||||
|
||||
|
||||
void UGCS_CombatSystemComponent::SetReplicatedMontage(const FGCS_PlayMontageRequest& Request)
|
||||
{
|
||||
TimerHandle.Invalidate();
|
||||
ReplicatedMontageInfo.AnimMontage = Request.AnimMontage;
|
||||
ReplicatedMontageInfo.PlayRate = Request.PlayRate;
|
||||
ReplicatedMontageInfo.TriggeredTime = GetWorld()->GetGameState()->GetServerWorldTimeSeconds();
|
||||
ReplicatedMontageInfo.StartSectionName = Request.StartSectionName;
|
||||
|
||||
GetWorld()->GetTimerManager().SetTimer(TimerHandle, [&]()
|
||||
{
|
||||
ReplicatedMontageInfo.AnimMontage = nullptr;
|
||||
TimerHandle.Invalidate();
|
||||
}, Request.AnimMontage->GetPlayLength() * Request.PlayRate, false);
|
||||
|
||||
|
||||
OnRep_ReplicatedMontageInfo();
|
||||
}
|
||||
|
||||
//server tell me to play montage.
|
||||
void UGCS_CombatSystemComponent::OnRep_ReplicatedMontageInfo()
|
||||
{
|
||||
if (ReplicatedMontageInfo.AnimMontage == nullptr)
|
||||
{
|
||||
PredictedMontageInfo.AnimMontage = nullptr;
|
||||
return;
|
||||
}
|
||||
if (USkeletalMeshComponent* MeshComponent = GetCharacterMeshComponent())
|
||||
{
|
||||
UAnimMontage* MontageToPlay = ReplicatedMontageInfo.AnimMontage;
|
||||
float TimeDiff = GetWorld()->GetGameState()->GetServerWorldTimeSeconds() - ReplicatedMontageInfo.TriggeredTime;
|
||||
float StartTime = FMath::Clamp(TimeDiff, 0, ReplicatedMontageInfo.AnimMontage->GetPlayLength() * ReplicatedMontageInfo.PlayRate);
|
||||
|
||||
//If local montage ahead of replicated montage
|
||||
if (PredictedMontageInfo.AnimMontage != nullptr)
|
||||
{
|
||||
//And it's the same.
|
||||
if (ReplicatedMontageInfo.AnimMontage == PredictedMontageInfo.AnimMontage)
|
||||
{
|
||||
PredictedMontageInfo.AnimMontage = nullptr;
|
||||
return;
|
||||
}
|
||||
PredictedMontageInfo.AnimMontage = nullptr;
|
||||
}
|
||||
|
||||
MeshComponent->GetAnimInstance()->Montage_Play(MontageToPlay, ReplicatedMontageInfo.PlayRate, EMontagePlayReturnType::MontageLength, StartTime);
|
||||
}
|
||||
}
|
||||
|
||||
void UGCS_CombatSystemComponent::PlayPredictedMontage(const FGCS_PlayMontageRequest& Request)
|
||||
{
|
||||
PredictedMontageInfo.AnimMontage = Request.AnimMontage;
|
||||
PredictedMontageInfo.PlayRate = Request.PlayRate;
|
||||
PredictedMontageInfo.TriggeredTime = GetWorld()->GetGameState()->GetServerWorldTimeSeconds();
|
||||
PredictedMontageInfo.StartSectionName = Request.StartSectionName;
|
||||
if (USkeletalMeshComponent* MeshComponent = GetCharacterMeshComponent())
|
||||
{
|
||||
float Duration = MeshComponent->GetAnimInstance()->Montage_Play(Request.AnimMontage, Request.PlayRate, EMontagePlayReturnType::MontageLength, Request.StartTimeSeconds);
|
||||
if (Duration > 0)
|
||||
{
|
||||
// Start at a given Section.
|
||||
if (Request.StartSectionName != NAME_None)
|
||||
{
|
||||
MeshComponent->GetAnimInstance()->Montage_JumpToSection(Request.StartSectionName, Request.AnimMontage);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
USkeletalMeshComponent* UGCS_CombatSystemComponent::GetCharacterMeshComponent() const
|
||||
{
|
||||
static FName CharacterMeshTagName = "CharacterMesh";
|
||||
|
||||
return Cast<USkeletalMeshComponent>(GetOwner()->FindComponentByTag(USkeletalMeshComponent::StaticClass(), CharacterMeshTagName));
|
||||
}
|
||||
|
||||
void UGCS_CombatSystemComponent::OnGlobalPreGameplayEffectSpecApply(FGameplayEffectSpec& Spec, UAbilitySystemComponent* AbilitySystemComponent)
|
||||
{
|
||||
if (IsValid(CombatFlow))
|
||||
{
|
||||
FGameplayTagContainer DynamicTags;
|
||||
CombatFlow->HandlePreGameplayEffectSpecApply(Spec, AbilitySystemComponent, DynamicTags);
|
||||
if (!DynamicTags.IsEmpty())
|
||||
{
|
||||
Spec.AppendDynamicAssetTags(DynamicTags);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UGCS_CombatSystemComponent::OnRep_CombatFlow()
|
||||
{
|
||||
CombatFlow->Initialize(GetOwner());
|
||||
UE_LOG(LogGCS, Display, TEXT("Combat flow replicated for %s"), *GetOwner()->GetName());
|
||||
}
|
||||
|
||||
int32 UGCS_CombatSystemComponent::GetComboStep() const
|
||||
{
|
||||
return ComboStep;
|
||||
}
|
||||
|
||||
void UGCS_CombatSystemComponent::UpdateComboStep(int32 NewComboStep)
|
||||
{
|
||||
if (NewComboStep >= 0)
|
||||
{
|
||||
UpdateComboStep(NewComboStep, true);
|
||||
}
|
||||
}
|
||||
|
||||
void UGCS_CombatSystemComponent::ResetComboState()
|
||||
{
|
||||
if (ComboStep != 0)
|
||||
{
|
||||
UpdateComboStep(0,true);
|
||||
}
|
||||
}
|
||||
|
||||
void UGCS_CombatSystemComponent::UpdateComboStep(int32 NewComboStep, bool bSendRpc)
|
||||
{
|
||||
if (ComboStep == NewComboStep || GetOwner()->GetLocalRole() <= ROLE_SimulatedProxy || ComboStep == NewComboStep)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const auto PrevComboStep{ComboStep};
|
||||
|
||||
ComboStep = NewComboStep;
|
||||
|
||||
MARK_PROPERTY_DIRTY_FROM_NAME(ThisClass, ComboStep, this)
|
||||
|
||||
OnComboStepChanged(PrevComboStep);
|
||||
|
||||
if (bSendRpc)
|
||||
{
|
||||
if (GetOwner()->GetLocalRole() >= ROLE_Authority)
|
||||
{
|
||||
ClientUpdateComboStep(ComboStep);
|
||||
}
|
||||
else
|
||||
{
|
||||
ServerUpdateComboStep(ComboStep);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UGCS_CombatSystemComponent::OnReplicated_ComboStep(int32 PrevComboStep)
|
||||
{
|
||||
OnComboStepChanged(PrevComboStep);
|
||||
}
|
||||
|
||||
void UGCS_CombatSystemComponent::ClientUpdateComboStep_Implementation(int32 NewComboStep)
|
||||
{
|
||||
UpdateComboStep(NewComboStep, false);
|
||||
}
|
||||
|
||||
bool UGCS_CombatSystemComponent::ClientUpdateComboStep_Validate(int32 NewComboStep)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
void UGCS_CombatSystemComponent::ServerUpdateComboStep_Implementation(int32 NewComboStep)
|
||||
{
|
||||
UpdateComboStep(NewComboStep, false);
|
||||
}
|
||||
|
||||
bool UGCS_CombatSystemComponent::ServerUpdateComboStep_Validate(int32 NewComboStep)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
void UGCS_CombatSystemComponent::OnComboStepChanged_Implementation(int32 PrevComboStep)
|
||||
{
|
||||
OnComboStepChangedEvent.Broadcast(PrevComboStep);
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
|
||||
#include "GCS_CombatSystemSettings.h"
|
||||
|
||||
const UGCS_CombatSystemSettings* UGCS_CombatSystemSettings::Get()
|
||||
{
|
||||
return GetDefault<UGCS_CombatSystemSettings>();
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
|
||||
#include "GCS_EffectCauserInterface.h"
|
||||
|
||||
|
||||
// Add default functionality here for any IGCS_EffectCauserInterface functions that are not pure virtual.
|
||||
@@ -0,0 +1,11 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
|
||||
#include "GCS_GameplayTags.h"
|
||||
|
||||
namespace GCS_BulletLaunch
|
||||
{
|
||||
UE_DEFINE_GAMEPLAY_TAG_COMMENT(Always, "GGF.Combat.Bullet.LaunchCond.Always", "Always generate bullet");
|
||||
UE_DEFINE_GAMEPLAY_TAG_COMMENT(DidNotHitPawn, "GGF.Combat.Bullet.LaunchCond.DidNotHitPawn", "Only generate bullet if not hit any pawn");
|
||||
UE_DEFINE_GAMEPLAY_TAG_COMMENT(HitPawn, "GGF.Combat.Bullet.LaunchCond.HitPawn", "Only generate bullet if hit any pawn");
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
|
||||
#include "GCS_LogChannels.h"
|
||||
#include "Engine/EngineTypes.h"
|
||||
#include "GameFramework/Actor.h"
|
||||
#include "Components/ActorComponent.h"
|
||||
#include "GameplayTask.h"
|
||||
#include "Abilities/GameplayAbility.h"
|
||||
|
||||
DEFINE_LOG_CATEGORY(LogGCS)
|
||||
DEFINE_LOG_CATEGORY(LogGCS_Targeting)
|
||||
DEFINE_LOG_CATEGORY(LogGCS_Collision)
|
||||
DEFINE_LOG_CATEGORY(LogGCS_Trace)
|
||||
|
||||
|
||||
FString GetGCSLogContextString(const UObject* ContextObject)
|
||||
{
|
||||
ENetRole Role = ROLE_None;
|
||||
FString RoleName = TEXT("None");
|
||||
FString Name = "None";
|
||||
|
||||
if (const AActor* Actor = Cast<AActor>(ContextObject))
|
||||
{
|
||||
Role = Actor->GetLocalRole();
|
||||
Name = Actor->GetName();
|
||||
}
|
||||
else if (const UActorComponent* Component = Cast<UActorComponent>(ContextObject))
|
||||
{
|
||||
Role = Component->GetOwnerRole();
|
||||
Name = Component->GetOwner()->GetName();
|
||||
}
|
||||
else if (const UGameplayTask* Task = Cast<UGameplayTask>(ContextObject))
|
||||
{
|
||||
Role = Task->GetAvatarActor()->GetLocalRole();
|
||||
Name = Task->GetAvatarActor()->GetName();
|
||||
}
|
||||
else if (const UGameplayAbility* Ability = Cast<UGameplayAbility>(ContextObject))
|
||||
{
|
||||
Role = Ability->GetAvatarActorFromActorInfo()->GetLocalRole();
|
||||
Name = Ability->GetAvatarActorFromActorInfo()->GetName();
|
||||
}
|
||||
else if (IsValid(ContextObject))
|
||||
{
|
||||
Name = ContextObject->GetName();
|
||||
}
|
||||
|
||||
if (Role != ROLE_None)
|
||||
{
|
||||
RoleName = (Role == ROLE_Authority) ? TEXT("Server") : TEXT("Client");
|
||||
}
|
||||
return FString::Printf(TEXT("[%s] (%s)"), *RoleName, *Name);
|
||||
}
|
||||
|
||||
|
||||
FString GetClientServerContextString(UObject* ContextObject)
|
||||
{
|
||||
ENetRole Role = ROLE_None;
|
||||
|
||||
if (AActor* Actor = Cast<AActor>(ContextObject))
|
||||
{
|
||||
Role = Actor->GetLocalRole();
|
||||
}
|
||||
else if (UActorComponent* Component = Cast<UActorComponent>(ContextObject))
|
||||
{
|
||||
Role = Component->GetOwnerRole();
|
||||
}
|
||||
|
||||
if (Role != ROLE_None)
|
||||
{
|
||||
return (Role == ROLE_Authority) ? TEXT("Server") : TEXT("Client");
|
||||
}
|
||||
#if WITH_EDITOR
|
||||
if (GIsEditor)
|
||||
{
|
||||
extern ENGINE_API FString GPlayInEditorContextString;
|
||||
return GPlayInEditorContextString;
|
||||
}
|
||||
#endif
|
||||
|
||||
return TEXT("[]");
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
#include "GenericCombatSystem.h"
|
||||
|
||||
#define LOCTEXT_NAMESPACE "FGenericCombatSystemModule"
|
||||
|
||||
void FGenericCombatSystemModule::StartupModule()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void FGenericCombatSystemModule::ShutdownModule()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
#undef LOCTEXT_NAMESPACE
|
||||
|
||||
IMPLEMENT_MODULE(FGenericCombatSystemModule, GenericCombatSystem)
|
||||
@@ -0,0 +1,28 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
|
||||
#include "Notifies/GCS_ANS_AttackTrace.h"
|
||||
|
||||
#include "CombatFlow/GCS_AttackRequest.h"
|
||||
#include "UObject/ObjectSaveContext.h"
|
||||
|
||||
|
||||
UGCS_ANS_AttackTrace::UGCS_ANS_AttackTrace(const FObjectInitializer& ObjectInitializer): Super(ObjectInitializer)
|
||||
{
|
||||
#if WITH_EDITORONLY_DATA
|
||||
bShouldFireInEditor = false;
|
||||
#endif
|
||||
AttackRequest = ObjectInitializer.CreateDefaultSubobject<UGCS_AttackRequest_Melee>(this, TEXT("AttackRequest"));
|
||||
}
|
||||
|
||||
void UGCS_ANS_AttackTrace::PostInitProperties()
|
||||
{
|
||||
Super::PostInitProperties();
|
||||
}
|
||||
|
||||
#if WITH_EDITORONLY_DATA
|
||||
void UGCS_ANS_AttackTrace::PreSave(FObjectPreSaveContext SaveContext)
|
||||
{
|
||||
Super::PreSave(SaveContext);
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,35 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
|
||||
#include "Notifies/GCS_ANS_BulletTrace.h"
|
||||
|
||||
#include "CombatFlow/GCS_AttackRequest.h"
|
||||
#include "UObject/ObjectSaveContext.h"
|
||||
|
||||
|
||||
UGCS_ANS_BulletTrace::UGCS_ANS_BulletTrace(const FObjectInitializer& ObjectInitializer): Super(ObjectInitializer)
|
||||
{
|
||||
#if WITH_EDITORONLY_DATA
|
||||
bShouldFireInEditor = false;
|
||||
#endif
|
||||
AttackRequest = ObjectInitializer.CreateDefaultSubobject<UGCS_AttackRequest_Bullet>(this, TEXT("AttackRequest"));
|
||||
}
|
||||
|
||||
#if WITH_EDITOR
|
||||
#include "Misc/DataValidation.h"
|
||||
|
||||
EDataValidationResult UGCS_ANS_BulletTrace::IsDataValid(FDataValidationContext& Context) const
|
||||
{
|
||||
if (AttackRequest && AttackRequest->bRequireTargeting && AttackRequest->TargetingPreset == nullptr)
|
||||
{
|
||||
Context.AddError(FText::FromString(TEXT("TargetingPreset is required!")));
|
||||
return EDataValidationResult::Invalid;
|
||||
}
|
||||
return Super::IsDataValid(Context);
|
||||
}
|
||||
|
||||
void UGCS_ANS_BulletTrace::PreSave(FObjectPreSaveContext SaveContext)
|
||||
{
|
||||
Super::PreSave(SaveContext);
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,100 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
|
||||
#include "Notifies/GCS_ANS_MovementCancellation.h"
|
||||
#include "Components/SkeletalMeshComponent.h"
|
||||
#include "Animation/AnimInstance.h"
|
||||
#include "GameFramework/Character.h"
|
||||
#include "GameFramework/CharacterMovementComponent.h"
|
||||
|
||||
UGCS_ANS_MovementCancellation::UGCS_ANS_MovementCancellation(const FObjectInitializer& ObjectInitializer): Super(ObjectInitializer)
|
||||
{
|
||||
bIsNativeBranchingPoint = true;
|
||||
}
|
||||
|
||||
void UGCS_ANS_MovementCancellation::BranchingPointNotifyBegin(FBranchingPointNotifyPayload& BranchingPointPayload)
|
||||
{
|
||||
Super::BranchingPointNotifyBegin(BranchingPointPayload);
|
||||
|
||||
IsRootMotionDisabled = false;
|
||||
|
||||
if (USkeletalMeshComponent* MeshComp = BranchingPointPayload.SkelMeshComponent)
|
||||
{
|
||||
if (UAnimInstance* AnimInstance = MeshComp->GetAnimInstance())
|
||||
{
|
||||
if (FAnimMontageInstance* MontageInstance = AnimInstance->GetMontageInstanceForID(BranchingPointPayload.MontageInstanceID))
|
||||
{
|
||||
if (ACharacter* Character = Cast<ACharacter>(MeshComp->GetOwner()))
|
||||
{
|
||||
if (Character->GetCharacterMovement()->GetCurrentAcceleration().SizeSquared2D() > 10.0)
|
||||
{
|
||||
MontageInstance->PushDisableRootMotion();
|
||||
|
||||
IsRootMotionDisabled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UGCS_ANS_MovementCancellation::BranchingPointNotifyTick(FBranchingPointNotifyPayload& BranchingPointPayload, float FrameDeltaTime)
|
||||
{
|
||||
Super::BranchingPointNotifyTick(BranchingPointPayload, FrameDeltaTime);
|
||||
if (USkeletalMeshComponent* MeshComp = BranchingPointPayload.SkelMeshComponent)
|
||||
{
|
||||
if (UAnimInstance* AnimInstance = MeshComp->GetAnimInstance())
|
||||
{
|
||||
if (FAnimMontageInstance* MontageInstance = AnimInstance->GetMontageInstanceForID(BranchingPointPayload.MontageInstanceID))
|
||||
{
|
||||
if (ACharacter* Character = Cast<ACharacter>(MeshComp->GetOwner())) {
|
||||
if (Character->GetCharacterMovement()->GetCurrentAcceleration().SizeSquared2D() > 10.0) {
|
||||
if (!IsRootMotionDisabled) {
|
||||
MontageInstance->PushDisableRootMotion();
|
||||
|
||||
IsRootMotionDisabled = true;
|
||||
}
|
||||
}
|
||||
else if (IsRootMotionDisabled) {
|
||||
MontageInstance->PopDisableRootMotion();
|
||||
|
||||
IsRootMotionDisabled = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UGCS_ANS_MovementCancellation::BranchingPointNotifyEnd(FBranchingPointNotifyPayload& BranchingPointPayload)
|
||||
{
|
||||
Super::BranchingPointNotifyEnd(BranchingPointPayload);
|
||||
if (USkeletalMeshComponent* MeshComp = BranchingPointPayload.SkelMeshComponent)
|
||||
{
|
||||
if (UAnimInstance* AnimInstance = MeshComp->GetAnimInstance())
|
||||
{
|
||||
if (FAnimMontageInstance* MontageInstance = AnimInstance->GetMontageInstanceForID(BranchingPointPayload.MontageInstanceID))
|
||||
{
|
||||
if (IsRootMotionDisabled) {
|
||||
IsRootMotionDisabled = false;
|
||||
|
||||
MontageInstance->PopDisableRootMotion();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool UGCS_ANS_MovementCancellation::IsMoving_Implementation(USkeletalMeshComponent* MeshComp) const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
#if WITH_EDITOR
|
||||
|
||||
|
||||
bool UGCS_ANS_MovementCancellation::CanBePlaced(UAnimSequenceBase* Animation) const
|
||||
{
|
||||
return (Animation && Animation->IsA(UAnimMontage::StaticClass()));
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,51 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
|
||||
#include "Targeting/Filters/GCS_TargetingFilterTask_Affiliation.h"
|
||||
#include "GCS_CombatSystemSettings.h"
|
||||
#include "GameFramework/Pawn.h"
|
||||
#include "GameFramework/Controller.h"
|
||||
#include "Team/GCS_CombatTeamAgentInterface.h"
|
||||
#include "Utility/GCS_CombatFunctionLibrary.h"
|
||||
|
||||
bool UGCS_TargetingFilterTask_Affiliation::ShouldFilterTarget(const FTargetingRequestHandle& TargetingHandle, const FTargetingDefaultResultData& TargetData) const
|
||||
{
|
||||
if (const UGCS_CombatSystemSettings* Settings = UGCS_CombatSystemSettings::Get())
|
||||
{
|
||||
if (Settings->bDisableAffiliationCheck)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
FGenericTeamId SourceTeamId = GetSourceTeamId(TargetingHandle, TargetData);
|
||||
FGenericTeamId TargetTeamId = GetTargetTeamId(TargetingHandle, TargetData);
|
||||
|
||||
if (TargetTeamId == FGenericTeamId::NoTeam && bIgnoreTargetWithNoTeam)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return FAISenseAffiliationFilter::ShouldSenseTeam(SourceTeamId, TargetTeamId, DetectionByAffiliation.GetAsFlags()) == false;
|
||||
}
|
||||
|
||||
FGenericTeamId UGCS_TargetingFilterTask_Affiliation::GetSourceTeamId(const FTargetingRequestHandle& TargetingHandle, const FTargetingDefaultResultData& TargetData) const
|
||||
{
|
||||
if (const FTargetingSourceContext* SourceContext = FTargetingSourceContext::Find(TargetingHandle))
|
||||
{
|
||||
AActor* Actor = SourceContext->InstigatorActor ? SourceContext->InstigatorActor : SourceContext->SourceActor;
|
||||
if (IsValid(Actor))
|
||||
{
|
||||
return UGCS_CombatFunctionLibrary::QueryCombatTeamId(Actor, true, true);;
|
||||
}
|
||||
}
|
||||
return FGenericTeamId::NoTeam;
|
||||
}
|
||||
|
||||
FGenericTeamId UGCS_TargetingFilterTask_Affiliation::GetTargetTeamId(const FTargetingRequestHandle& TargetingHandle, const FTargetingDefaultResultData& TargetData) const
|
||||
{
|
||||
if (const AActor* TargetActor = TargetData.HitResult.GetActor())
|
||||
{
|
||||
return UGCS_CombatFunctionLibrary::QueryCombatTeamId(TargetActor, true, true);;
|
||||
}
|
||||
return FGenericTeamId::NoTeam;
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
|
||||
#include "Targeting/Filters/GCS_TargetingFilterTask_IsDead.h"
|
||||
|
||||
#include "GCS_CombatEntityInterface.h"
|
||||
#include "Utility/GCS_CombatFunctionLibrary.h"
|
||||
|
||||
bool UGCS_TargetingFilterTask_IsDead::ShouldFilterTarget(const FTargetingRequestHandle& TargetingHandle, const FTargetingDefaultResultData& TargetData) const
|
||||
{
|
||||
AActor* TargetActor = TargetData.HitResult.GetActor();
|
||||
|
||||
if (UObject* Implementer = UGCS_CombatFunctionLibrary::GetCombatEntity(TargetActor))
|
||||
{
|
||||
return IGCS_CombatEntityInterface::Execute_IsDead(Implementer);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
|
||||
#include "Targeting/Filters/GCS_TargetingFilterTask_TagsRequirements.h"
|
||||
|
||||
#include "AbilitySystemComponent.h"
|
||||
#include "AbilitySystemGlobals.h"
|
||||
|
||||
bool UGCS_TargetingFilterTask_TagsRequirements::ShouldFilterTarget(const FTargetingRequestHandle& TargetingHandle, const FTargetingDefaultResultData& TargetData) const
|
||||
{
|
||||
const AActor* TargetActor = TargetData.HitResult.GetActor();
|
||||
|
||||
if (UAbilitySystemComponent* ASC = UAbilitySystemGlobals::GetAbilitySystemComponentFromActor(TargetActor))
|
||||
{
|
||||
FGameplayTagContainer ActorTags;
|
||||
ASC->GetOwnedGameplayTags(ActorTags);
|
||||
|
||||
return bInvert ? !TagQuery.Matches(ActorTags) : TagQuery.Matches(ActorTags);
|
||||
}
|
||||
|
||||
if (bLookingForTagAssetInterface)
|
||||
{
|
||||
const IGameplayTagAssetInterface* TagAssetInterface = Cast<IGameplayTagAssetInterface>(TargetActor);
|
||||
if (!TagAssetInterface)
|
||||
{
|
||||
const TArray<UActorComponent*> Components = TargetActor->GetComponentsByInterface(UGameplayTagAssetInterface::StaticClass());
|
||||
TagAssetInterface = Components.IsValidIndex(0) ? Cast<IGameplayTagAssetInterface>(Components[0]) : nullptr;
|
||||
}
|
||||
|
||||
if (TagAssetInterface)
|
||||
{
|
||||
FGameplayTagContainer ActorTags;
|
||||
TagAssetInterface->GetOwnedGameplayTags(ActorTags);
|
||||
|
||||
return bInvert ? !TagQuery.Matches(ActorTags) : TagQuery.Matches(ActorTags);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
|
||||
#include "Targeting/Filters/GCS_TargetingFilterTask_TraceInstance.h"
|
||||
|
||||
#include "Collision/DEPRECATED_GCS_CollisionTraceInstance.h"
|
||||
|
||||
bool UGCS_TargetingFilterTask_TraceInstance::ShouldFilterTarget(const FTargetingRequestHandle& TargetingHandle, const FTargetingDefaultResultData& TargetData) const
|
||||
{
|
||||
const AActor* TargetActor = TargetData.HitResult.GetActor();
|
||||
|
||||
if (const FTargetingSourceContext* SourceContext = FTargetingSourceContext::Find(TargetingHandle))
|
||||
{
|
||||
if (const UDEPRECATED_GCS_CollisionTraceInstance* TraceInstance = Cast<UDEPRECATED_GCS_CollisionTraceInstance>(SourceContext->SourceObject))
|
||||
{
|
||||
return !TraceInstance->CanHitActor(TargetActor);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
|
||||
#include "Targeting/GCS_TargetingFunctionLibrary.h"
|
||||
#include "Components/MeshComponent.h"
|
||||
|
||||
FTargetingSourceContext UGCS_TargetingFunctionLibrary::GetTargetingSourceContext(FTargetingRequestHandle TargetingHandle)
|
||||
{
|
||||
if (TargetingHandle.IsValid())
|
||||
{
|
||||
if (FTargetingSourceContext* SourceContext = FTargetingSourceContext::Find(TargetingHandle))
|
||||
{
|
||||
return *SourceContext;
|
||||
}
|
||||
}
|
||||
|
||||
return FTargetingSourceContext();
|
||||
}
|
||||
|
||||
FString UGCS_TargetingFunctionLibrary::GetTargetingSourceContextDebugString(FTargetingRequestHandle TargetingHandle)
|
||||
{
|
||||
if (TargetingHandle.IsValid())
|
||||
{
|
||||
if (FTargetingSourceContext* SourceContext = FTargetingSourceContext::Find(TargetingHandle))
|
||||
{
|
||||
return FString::Format(TEXT("ctx(actor:{0},instigator:{1},source:{2})"), {
|
||||
*GetNameSafe(SourceContext->SourceActor), *GetNameSafe(SourceContext->InstigatorActor), *GetNameSafe(SourceContext->SourceObject)
|
||||
});
|
||||
}
|
||||
}
|
||||
return TEXT("None");
|
||||
}
|
||||
|
||||
void UGCS_TargetingFunctionLibrary::GetTargetingResultsActors(FTargetingRequestHandle TargetingHandle, TArray<AActor*>& Targets)
|
||||
{
|
||||
if (TargetingHandle.IsValid())
|
||||
{
|
||||
if (FTargetingDefaultResultsSet* Results = FTargetingDefaultResultsSet::Find(TargetingHandle))
|
||||
{
|
||||
for (const FTargetingDefaultResultData& ResultData : Results->TargetResults)
|
||||
{
|
||||
if (AActor* Target = ResultData.HitResult.GetActor())
|
||||
{
|
||||
Targets.Add(Target);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UGCS_TargetingFunctionLibrary::GetTargetingResults(FTargetingRequestHandle TargetingHandle, TArray<FHitResult>& OutTargets)
|
||||
{
|
||||
if (TargetingHandle.IsValid())
|
||||
{
|
||||
if (FTargetingDefaultResultsSet* Results = FTargetingDefaultResultsSet::Find(TargetingHandle))
|
||||
{
|
||||
for (const FTargetingDefaultResultData& ResultData : Results->TargetResults)
|
||||
{
|
||||
OutTargets.Add(ResultData.HitResult);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FTargetingSourceContext UGCS_TargetingFunctionLibrary::ConvertTargetingLocationInfoToSourceContext(FGameplayAbilityTargetingLocationInfo LocationInfo)
|
||||
{
|
||||
FTargetingSourceContext Context = FTargetingSourceContext();
|
||||
|
||||
//Return or calculate based on LocationType.
|
||||
switch (LocationInfo.LocationType)
|
||||
{
|
||||
case EGameplayAbilityTargetingLocationType::ActorTransform:
|
||||
if (LocationInfo.SourceActor)
|
||||
{
|
||||
Context.SourceActor = LocationInfo.SourceActor;
|
||||
}
|
||||
break;
|
||||
case EGameplayAbilityTargetingLocationType::SocketTransform:
|
||||
if (LocationInfo.SourceComponent)
|
||||
{
|
||||
// Bad socket name will just return component transform anyway, so we're safe
|
||||
Context.SourceLocation = LocationInfo.SourceComponent->GetSocketTransform(LocationInfo.SourceSocketName).GetLocation();
|
||||
}
|
||||
break;
|
||||
case EGameplayAbilityTargetingLocationType::LiteralTransform:
|
||||
Context.SourceLocation = LocationInfo.LiteralTransform.GetLocation();
|
||||
default:
|
||||
check(false);
|
||||
break;
|
||||
}
|
||||
|
||||
return Context;
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
|
||||
#include "Targeting/GCS_TargetingSourceInterface.h"
|
||||
|
||||
|
||||
// Add default functionality here for any IGCS_TargetingSourceInterface functions that are not pure virtual.
|
||||
@@ -0,0 +1,412 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
|
||||
#include "Targeting/GCS_TargetingSystemComponent.h"
|
||||
|
||||
#include "GCS_LogChannels.h"
|
||||
#include "Kismet/KismetMathLibrary.h"
|
||||
#include "Camera/CameraComponent.h"
|
||||
#include "GameFramework/Character.h"
|
||||
#include "Camera/PlayerCameraManager.h"
|
||||
#include "Net/UnrealNetwork.h"
|
||||
#include "TargetingSystem/TargetingSubsystem.h"
|
||||
|
||||
UGCS_TargetingSystemComponent::UGCS_TargetingSystemComponent(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer)
|
||||
{
|
||||
PrimaryComponentTick.bCanEverTick = true;
|
||||
SetIsReplicatedByDefault(true);
|
||||
}
|
||||
|
||||
UGCS_TargetingSystemComponent* UGCS_TargetingSystemComponent::GetTargetingSystemComponent(const AActor* Actor)
|
||||
{
|
||||
return Actor ? Actor->FindComponentByClass<UGCS_TargetingSystemComponent>() : nullptr;
|
||||
}
|
||||
|
||||
void UGCS_TargetingSystemComponent::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
|
||||
{
|
||||
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
|
||||
|
||||
DOREPLIFETIME_CONDITION(ThisClass, TargetedActor, COND_OwnerOnly);
|
||||
}
|
||||
|
||||
|
||||
// Called when the game starts
|
||||
void UGCS_TargetingSystemComponent::BeginPlay()
|
||||
{
|
||||
Super::BeginPlay();
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
|
||||
// Called every frame
|
||||
void UGCS_TargetingSystemComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
|
||||
{
|
||||
Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
|
||||
|
||||
RefreshTargeting(DeltaTime);
|
||||
}
|
||||
|
||||
void UGCS_TargetingSystemComponent::RefreshTargeting(float DeltaTime)
|
||||
{
|
||||
if (GetOwnerRole() >= ROLE_AutonomousProxy)
|
||||
{
|
||||
if (bAutoUpdatePotentialTargets || IsValid(TargetedActor))
|
||||
{
|
||||
RefreshPotentialTargets();
|
||||
}
|
||||
|
||||
bool LocalBool = PotentialTargets.Contains(TargetedActor);
|
||||
|
||||
// Current TargetedActor no longer consider valid.
|
||||
if (TargetedActor && !LocalBool)
|
||||
{
|
||||
OnLockOff();
|
||||
SetTargetedActor(nullptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UGCS_TargetingSystemComponent::SearchForActorToTarget()
|
||||
{
|
||||
RefreshPotentialTargets();
|
||||
SelectFromPotentialTargets();
|
||||
//No target found/unlocked.
|
||||
if (!IsValid(TargetedActor) && !bAutoUpdatePotentialTargets)
|
||||
{
|
||||
PotentialTargets.Empty();
|
||||
}
|
||||
}
|
||||
|
||||
AActor* UGCS_TargetingSystemComponent::SelectClosestActorFromPotentialTargets(float Radius) const
|
||||
{
|
||||
// 检查 Owner 是否有效
|
||||
if (!GetOwner())
|
||||
{
|
||||
UE_LOG(LogTemp, Warning, TEXT("SelectClosestActorFromPotentialTargets: Owner is null"));
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// 检查 PotentialTargets 是否为空
|
||||
if (PotentialTargets.Num() == 0)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// 过滤有效 Actor 并在范围内
|
||||
TArray<AActor*> FilteredPotentialTargets;
|
||||
const FVector OwnerLocation = GetOwner()->GetActorLocation();
|
||||
|
||||
for (AActor* Actor : PotentialTargets)
|
||||
{
|
||||
// 检查 Actor 是否有效且未被销毁
|
||||
if (IsValid(Actor))
|
||||
{
|
||||
float Distance = FVector::Dist(OwnerLocation, Actor->GetActorLocation());
|
||||
if (Distance <= Radius)
|
||||
{
|
||||
FilteredPotentialTargets.Add(Actor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 如果过滤后没有有效目标
|
||||
if (FilteredPotentialTargets.Num() == 0)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// 寻找最近的 Actor
|
||||
AActor* ClosestActor = FilteredPotentialTargets[0];
|
||||
float MinDistance = FVector::Dist(OwnerLocation, ClosestActor->GetActorLocation());
|
||||
|
||||
for (int32 i = 1; i < FilteredPotentialTargets.Num(); ++i)
|
||||
{
|
||||
AActor* CurrentActor = FilteredPotentialTargets[i];
|
||||
if (IsValid(CurrentActor))
|
||||
{
|
||||
float Distance = FVector::Dist(OwnerLocation, CurrentActor->GetActorLocation());
|
||||
if (Distance < MinDistance)
|
||||
{
|
||||
MinDistance = Distance;
|
||||
ClosestActor = CurrentActor;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ClosestActor;
|
||||
}
|
||||
|
||||
bool UGCS_TargetingSystemComponent::FilterActorsWithPreset(UTargetingPreset* InTargetingPreset, const TArray<AActor*> InTargets, TArray<AActor*>& OutActors)
|
||||
{
|
||||
if (InTargetingPreset == nullptr)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (UTargetingSubsystem* TargetingSubsystem = UTargetingSubsystem::Get(GetWorld()))
|
||||
{
|
||||
FTargetingSourceContext SourceContext;
|
||||
SourceContext.SourceActor = GetOwner();
|
||||
|
||||
FTargetingRequestHandle TargetingHandle = UTargetingSubsystem::MakeTargetRequestHandle(TargetingPreset, SourceContext);
|
||||
|
||||
if (TargetingHandle.IsValid() && InTargets.Num() > 0)
|
||||
{
|
||||
FTargetingDefaultResultsSet& TargetingResults = FTargetingDefaultResultsSet::FindOrAdd(TargetingHandle);
|
||||
for (AActor* Target : InTargets)
|
||||
{
|
||||
if (!Target)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
bool bAddResult = !TargetingResults.TargetResults.ContainsByPredicate([Target](const FTargetingDefaultResultData& Data) -> bool
|
||||
{
|
||||
return (Data.HitResult.GetActor() == Target);
|
||||
});
|
||||
|
||||
if (bAddResult)
|
||||
{
|
||||
FTargetingDefaultResultData* ResultData = new(TargetingResults.TargetResults) FTargetingDefaultResultData();
|
||||
ResultData->HitResult.HitObjectHandle = FActorInstanceHandle(Target);
|
||||
ResultData->HitResult.Location = Target->GetActorLocation();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
FTargetingRequestDelegate Delegate = FTargetingRequestDelegate::CreateWeakLambda(this, [&](FTargetingRequestHandle InTargetingHandle)
|
||||
{
|
||||
TargetingSubsystem->GetTargetingResultsActors(InTargetingHandle, OutActors);
|
||||
});
|
||||
|
||||
FTargetingImmediateTaskData& ImmeidateTaskData = FTargetingImmediateTaskData::FindOrAdd(TargetingHandle);
|
||||
ImmeidateTaskData.bReleaseOnCompletion = true;
|
||||
|
||||
TargetingSubsystem->ExecuteTargetingRequestWithHandle(TargetingHandle, Delegate);
|
||||
}
|
||||
|
||||
return !OutActors.IsEmpty();
|
||||
}
|
||||
|
||||
void UGCS_TargetingSystemComponent::SetTargetedActor(AActor* NewActor)
|
||||
{
|
||||
SetTargetedActor(NewActor, true);
|
||||
}
|
||||
|
||||
void UGCS_TargetingSystemComponent::SetTargetedActor(AActor* NewActor, bool bSendRpc)
|
||||
{
|
||||
if (GetOwnerRole() < ROLE_AutonomousProxy)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (bSendRpc)
|
||||
{
|
||||
if (GetOwnerRole() >= ROLE_Authority)
|
||||
{
|
||||
ClientSetTargetedActor(NewActor);
|
||||
}
|
||||
else
|
||||
{
|
||||
ServerSetTargetedActor(NewActor);
|
||||
}
|
||||
}
|
||||
|
||||
TargetedActor = NewActor;
|
||||
}
|
||||
|
||||
void UGCS_TargetingSystemComponent::ClientSetTargetedActor_Implementation(AActor* NewActor)
|
||||
{
|
||||
SetTargetedActor(NewActor, false);
|
||||
}
|
||||
|
||||
void UGCS_TargetingSystemComponent::ServerSetTargetedActor_Implementation(AActor* NewActor)
|
||||
{
|
||||
SetTargetedActor(NewActor, false);
|
||||
}
|
||||
|
||||
void UGCS_TargetingSystemComponent::OnLockOff_Implementation()
|
||||
{
|
||||
OnTargetLockOffEvent.Broadcast(TargetedActor);
|
||||
}
|
||||
|
||||
void UGCS_TargetingSystemComponent::OnLockOn_Implementation()
|
||||
{
|
||||
OnTargetLockOnEvent.Broadcast(TargetedActor);
|
||||
}
|
||||
|
||||
void UGCS_TargetingSystemComponent::SelectFromPotentialTargets()
|
||||
{
|
||||
if (!IsValid(TargetedActor))
|
||||
{
|
||||
TMap<AActor*, float> LocalPotentialTargets;
|
||||
|
||||
for (TObjectPtr<AActor>& Elem : PotentialTargets)
|
||||
{
|
||||
if (CanBeTargeted(Elem))
|
||||
{
|
||||
LocalPotentialTargets.Add(Elem, UKismetMathLibrary::Abs(CalculateViewAngle(Elem)));
|
||||
}
|
||||
}
|
||||
|
||||
if (LocalPotentialTargets.Num() > 0)
|
||||
{
|
||||
TArray<AActor*> LocalTargets;
|
||||
TArray<float> LocalAngles;
|
||||
LocalPotentialTargets.GenerateKeyArray(LocalTargets);
|
||||
LocalPotentialTargets.GenerateValueArray(LocalAngles);
|
||||
|
||||
int32 MinIndex;
|
||||
const float MaxValue = FMath::Min<float>(LocalAngles, &MinIndex);
|
||||
SetTargetedActor(LocalTargets[MinIndex]);
|
||||
OnLockOn();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
OnLockOff();
|
||||
SetTargetedActor(nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
void UGCS_TargetingSystemComponent::RefreshPotentialTargets()
|
||||
{
|
||||
DECLARE_SCOPE_CYCLE_COUNTER(TEXT("UGCS_TargetingSystemComponent::RefreshPotentialTargets"), UGCS_TargetingSystemComponent_RefreshPotentialTargets, STATGROUP_GCS)
|
||||
TRACE_CPUPROFILER_EVENT_SCOPE_STR(__FUNCTION__)
|
||||
if (TargetingPreset == nullptr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (UTargetingSubsystem* TargetingSubsystem = UTargetingSubsystem::Get(GetWorld()))
|
||||
{
|
||||
FTargetingSourceContext SourceContext;
|
||||
SourceContext.SourceActor = GetOwner();
|
||||
SourceContext.SourceObject = this;
|
||||
|
||||
FTargetingRequestHandle TargetingHandle = UTargetingSubsystem::MakeTargetRequestHandle(TargetingPreset, SourceContext);
|
||||
FTargetingRequestDelegate Delegate = FTargetingRequestDelegate::CreateWeakLambda(this, [this,TargetingSubsystem](FTargetingRequestHandle InTargetingHandle)
|
||||
{
|
||||
TArray<AActor*> Results;
|
||||
TargetingSubsystem->GetTargetingResultsActors(InTargetingHandle, Results);
|
||||
PotentialTargets.Empty();
|
||||
PotentialTargets = Results;
|
||||
});
|
||||
|
||||
if (bUseAsyncTargeting)
|
||||
{
|
||||
FTargetingAsyncTaskData& AsyncTaskData = FTargetingAsyncTaskData::FindOrAdd(TargetingHandle);
|
||||
AsyncTaskData.bReleaseOnCompletion = true;
|
||||
|
||||
TargetingSubsystem->StartAsyncTargetingRequestWithHandle(TargetingHandle, Delegate);
|
||||
}
|
||||
else
|
||||
{
|
||||
FTargetingImmediateTaskData& ImmeidateTaskData = FTargetingImmediateTaskData::FindOrAdd(TargetingHandle);
|
||||
ImmeidateTaskData.bReleaseOnCompletion = true;
|
||||
|
||||
TargetingSubsystem->ExecuteTargetingRequestWithHandle(TargetingHandle, Delegate);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool UGCS_TargetingSystemComponent::CanBeTargeted_Implementation(AActor* ActorToTarget)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
void UGCS_TargetingSystemComponent::StaticSwitchToNewTarget(bool RightDirection)
|
||||
{
|
||||
if (TargetedActor)
|
||||
{
|
||||
TMap<AActor*, float> LocalPotentialTargets;
|
||||
|
||||
for (TObjectPtr<AActor>& Elem : PotentialTargets)
|
||||
{
|
||||
if (Elem != TargetedActor)
|
||||
{
|
||||
float LocalDistance = UKismetMathLibrary::Vector_Distance(GetOwner()->GetActorLocation(), Elem->GetActorLocation());
|
||||
|
||||
FRotator RequiredRotation = UKismetMathLibrary::FindLookAtRotation(GetOwner()->GetActorLocation(), Elem->GetActorLocation());
|
||||
FRotator DeltaRotation;
|
||||
|
||||
ACharacter* LocalOwnerCharacter = Cast<ACharacter>(GetOwner());
|
||||
|
||||
DeltaRotation = UKismetMathLibrary::NormalizedDeltaRotator(LocalOwnerCharacter->GetControlRotation(), RequiredRotation);
|
||||
|
||||
if (RightDirection == 1)
|
||||
{
|
||||
if (DeltaRotation.Yaw < 0.f && DeltaRotation.Yaw > -100.f)
|
||||
{
|
||||
LocalPotentialTargets.Add(Elem, DeltaRotation.Yaw);
|
||||
}
|
||||
else
|
||||
{
|
||||
LocalPotentialTargets.Add(Elem, -10000.f);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (DeltaRotation.Yaw > 0.f && DeltaRotation.Yaw < 100.f)
|
||||
{
|
||||
LocalPotentialTargets.Add(Elem, DeltaRotation.Yaw);
|
||||
}
|
||||
else
|
||||
{
|
||||
LocalPotentialTargets.Add(Elem, 10000.f);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (LocalPotentialTargets.Num() > 0)
|
||||
{
|
||||
TArray<AActor*> LocalTargets;
|
||||
TArray<float> LocalAngles;
|
||||
LocalPotentialTargets.GenerateKeyArray(LocalTargets);
|
||||
LocalPotentialTargets.GenerateValueArray(LocalAngles);
|
||||
|
||||
if (RightDirection == 1)
|
||||
{
|
||||
int32 FoundIndex;
|
||||
const float MaxValue = FMath::Max<float>(LocalAngles, &FoundIndex);
|
||||
if (MaxValue > -10000.f)
|
||||
{
|
||||
OnLockOff();
|
||||
SetTargetedActor(LocalTargets[FoundIndex]);
|
||||
OnLockOn();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
int32 FoundIndex;
|
||||
const float MinValue = FMath::Min<float>(LocalAngles, &FoundIndex);
|
||||
if (MinValue < 10000.f)
|
||||
{
|
||||
OnLockOff();
|
||||
SetTargetedActor(LocalTargets[FoundIndex]);
|
||||
OnLockOn();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
float UGCS_TargetingSystemComponent::CalculateViewAngle(const AActor* TargetActor)
|
||||
{
|
||||
APawn* OwningPawn = GetPawn<APawn>();
|
||||
FRotator FinalRotation = FRotator::ZeroRotator;
|
||||
if (TargetActor && OwningPawn)
|
||||
{
|
||||
FRotator RequiredRotation = UKismetMathLibrary::FindLookAtRotation(OwningPawn->GetPawnViewLocation(), TargetActor->GetActorLocation());
|
||||
|
||||
FRotator DeltaRotation = UKismetMathLibrary::NormalizedDeltaRotator(RequiredRotation, OwningPawn->GetViewRotation());
|
||||
FinalRotation = DeltaRotation;
|
||||
}
|
||||
|
||||
return UKismetMathLibrary::Abs(UKismetMathLibrary::Abs(FinalRotation.Yaw) + UKismetMathLibrary::Abs(FinalRotation.Pitch));
|
||||
}
|
||||
@@ -0,0 +1,369 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
#include "Targeting/Selections/GCS_TargetingSelectionTask_LineTrace.h"
|
||||
|
||||
#include "CollisionQueryParams.h"
|
||||
#include "KismetTraceUtils.h"
|
||||
#include "Components/PrimitiveComponent.h"
|
||||
#include "Engine/EngineTypes.h"
|
||||
#include "Engine/World.h"
|
||||
#include "GameFramework/Actor.h"
|
||||
#include "GameFramework/Pawn.h"
|
||||
#include "Kismet/KismetSystemLibrary.h"
|
||||
#include "TargetingSystem/TargetingSubsystem.h"
|
||||
|
||||
#if ENABLE_DRAW_DEBUG
|
||||
#include "Engine/Canvas.h"
|
||||
#endif // ENABLE_DRAW_DEBUG
|
||||
|
||||
|
||||
UGCS_TargetingSelectionTask_LineTrace::UGCS_TargetingSelectionTask_LineTrace(const FObjectInitializer& ObjectInitializer)
|
||||
: Super(ObjectInitializer)
|
||||
{
|
||||
bComplexTrace = false;
|
||||
bIgnoreSourceActor = false;
|
||||
bIgnoreInstigatorActor = false;
|
||||
bGenerateDefaultHitResult = true;
|
||||
}
|
||||
|
||||
void UGCS_TargetingSelectionTask_LineTrace::Execute(const FTargetingRequestHandle& TargetingHandle) const
|
||||
{
|
||||
Super::Execute(TargetingHandle);
|
||||
|
||||
SetTaskAsyncState(TargetingHandle, ETargetingTaskAsyncState::Executing);
|
||||
|
||||
if (IsAsyncTargetingRequest(TargetingHandle))
|
||||
{
|
||||
ExecuteAsyncTrace(TargetingHandle);
|
||||
}
|
||||
else
|
||||
{
|
||||
ExecuteImmediateTrace(TargetingHandle);
|
||||
}
|
||||
}
|
||||
|
||||
FVector UGCS_TargetingSelectionTask_LineTrace::GetSourceLocation_Implementation(const FTargetingRequestHandle& TargetingHandle) const
|
||||
{
|
||||
if (const FTargetingSourceContext* SourceContext = FTargetingSourceContext::Find(TargetingHandle))
|
||||
{
|
||||
if (SourceContext->SourceActor)
|
||||
{
|
||||
return SourceContext->SourceActor->GetActorLocation();
|
||||
}
|
||||
|
||||
return SourceContext->SourceLocation;
|
||||
}
|
||||
|
||||
return FVector::ZeroVector;
|
||||
}
|
||||
|
||||
FVector UGCS_TargetingSelectionTask_LineTrace::GetSourceOffset_Implementation(const FTargetingRequestHandle& TargetingHandle) const
|
||||
{
|
||||
return DefaultSourceOffset;
|
||||
}
|
||||
|
||||
FVector UGCS_TargetingSelectionTask_LineTrace::GetTraceDirection_Implementation(const FTargetingRequestHandle& TargetingHandle) const
|
||||
{
|
||||
if (const FTargetingSourceContext* SourceContext = FTargetingSourceContext::Find(TargetingHandle))
|
||||
{
|
||||
if (SourceContext->SourceActor)
|
||||
{
|
||||
if (APawn* Pawn = Cast<APawn>(SourceContext->SourceActor))
|
||||
{
|
||||
return Pawn->GetControlRotation().Vector();
|
||||
}
|
||||
else
|
||||
{
|
||||
return SourceContext->SourceActor->GetActorForwardVector();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return FVector::ZeroVector;
|
||||
}
|
||||
|
||||
float UGCS_TargetingSelectionTask_LineTrace::GetTraceLength_Implementation(const FTargetingRequestHandle& TargetingHandle) const
|
||||
{
|
||||
return DefaultTraceLength.GetValueAtLevel(GetTraceLevel(TargetingHandle));
|
||||
}
|
||||
|
||||
void UGCS_TargetingSelectionTask_LineTrace::GetAdditionalActorsToIgnore_Implementation(const FTargetingRequestHandle& TargetingHandle, TArray<AActor*>& OutAdditionalActorsToIgnore) const
|
||||
{
|
||||
}
|
||||
|
||||
float UGCS_TargetingSelectionTask_LineTrace::GetTraceLevel_Implementation(const FTargetingRequestHandle& TargetingHandle) const
|
||||
{
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
void UGCS_TargetingSelectionTask_LineTrace::ExecuteImmediateTrace(const FTargetingRequestHandle& TargetingHandle) const
|
||||
{
|
||||
if (UWorld* World = GetSourceContextWorld(TargetingHandle))
|
||||
{
|
||||
#if ENABLE_DRAW_DEBUG
|
||||
ResetTraceResultsDebugString(TargetingHandle);
|
||||
#endif // ENABLE_DRAW_DEBUG
|
||||
|
||||
const FVector Direction = GetTraceDirection(TargetingHandle).GetSafeNormal();
|
||||
const FVector Start = (GetSourceLocation(TargetingHandle) + GetSourceOffset(TargetingHandle));
|
||||
const FVector End = Start + (Direction * GetTraceLength(TargetingHandle));
|
||||
|
||||
FCollisionQueryParams Params(SCENE_QUERY_STAT(ExecuteImmediateTrace), bComplexTrace);
|
||||
InitCollisionParams(TargetingHandle, Params);
|
||||
|
||||
bool bHasBlockingHit = false;
|
||||
TArray<FHitResult> Hits;
|
||||
if (CollisionProfileName.Name != TEXT("NoCollision"))
|
||||
{
|
||||
bHasBlockingHit = World->LineTraceMultiByProfile(Hits, Start, End, CollisionProfileName.Name, Params);
|
||||
}
|
||||
else
|
||||
{
|
||||
const ECollisionChannel CollisionChannel = UEngineTypes::ConvertToCollisionChannel(TraceChannel);
|
||||
bHasBlockingHit = World->LineTraceMultiByChannel(Hits, Start, End, CollisionChannel, Params);
|
||||
}
|
||||
|
||||
#if ENABLE_DRAW_DEBUG
|
||||
DrawDebugTrace(TargetingHandle, Start, End, bHasBlockingHit, Hits);
|
||||
#endif // ENABLE_DRAW_DEBUG
|
||||
|
||||
ProcessHitResults(TargetingHandle, Hits);
|
||||
}
|
||||
|
||||
SetTaskAsyncState(TargetingHandle, ETargetingTaskAsyncState::Completed);
|
||||
}
|
||||
|
||||
void UGCS_TargetingSelectionTask_LineTrace::ExecuteAsyncTrace(const FTargetingRequestHandle& TargetingHandle) const
|
||||
{
|
||||
if (UWorld* World = GetSourceContextWorld(TargetingHandle))
|
||||
{
|
||||
AActor* SourceActor = nullptr;
|
||||
if (const FTargetingSourceContext* SourceContext = FTargetingSourceContext::Find(TargetingHandle))
|
||||
{
|
||||
SourceActor = SourceContext->SourceActor;
|
||||
}
|
||||
const FVector Direction = GetTraceDirection(TargetingHandle).GetSafeNormal();
|
||||
const FVector Start = (GetSourceLocation(TargetingHandle) + GetSourceOffset(TargetingHandle));
|
||||
const FVector End = Start + (Direction * GetTraceLength(TargetingHandle));
|
||||
|
||||
FCollisionQueryParams Params(SCENE_QUERY_STAT(ExecuteAsyncTrace), bComplexTrace);
|
||||
InitCollisionParams(TargetingHandle, Params);
|
||||
|
||||
FTraceDelegate Delegate = FTraceDelegate::CreateUObject(this, &UGCS_TargetingSelectionTask_LineTrace::HandleAsyncTraceComplete, TargetingHandle);
|
||||
if (CollisionProfileName.Name != TEXT("NoCollision"))
|
||||
{
|
||||
World->AsyncLineTraceByProfile(EAsyncTraceType::Multi, Start, End, CollisionProfileName.Name, Params, &Delegate);
|
||||
}
|
||||
else
|
||||
{
|
||||
const ECollisionChannel CollisionChannel = UEngineTypes::ConvertToCollisionChannel(TraceChannel);
|
||||
World->AsyncLineTraceByChannel(EAsyncTraceType::Multi, Start, End, CollisionChannel, Params, FCollisionResponseParams::DefaultResponseParam, &Delegate);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
SetTaskAsyncState(TargetingHandle, ETargetingTaskAsyncState::Completed);
|
||||
}
|
||||
}
|
||||
|
||||
void UGCS_TargetingSelectionTask_LineTrace::HandleAsyncTraceComplete(const FTraceHandle& InTraceHandle, FTraceDatum& InTraceDatum, FTargetingRequestHandle TargetingHandle) const
|
||||
{
|
||||
if (TargetingHandle.IsValid())
|
||||
{
|
||||
#if ENABLE_DRAW_DEBUG
|
||||
ResetTraceResultsDebugString(TargetingHandle);
|
||||
|
||||
// We have to manually find if there is a blocking hit.
|
||||
bool bHasBlockingHit = false;
|
||||
for (const FHitResult& HitResult : InTraceDatum.OutHits)
|
||||
{
|
||||
if (HitResult.bBlockingHit)
|
||||
{
|
||||
bHasBlockingHit = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
DrawDebugTrace(TargetingHandle, InTraceDatum.Start, InTraceDatum.End, bHasBlockingHit, InTraceDatum.OutHits);
|
||||
|
||||
#endif // ENABLE_DRAW_DEBUG
|
||||
|
||||
ProcessHitResults(TargetingHandle, InTraceDatum.OutHits);
|
||||
}
|
||||
|
||||
SetTaskAsyncState(TargetingHandle, ETargetingTaskAsyncState::Completed);
|
||||
}
|
||||
|
||||
void UGCS_TargetingSelectionTask_LineTrace::ProcessHitResults(const FTargetingRequestHandle& TargetingHandle, const TArray<FHitResult>& Hits) const
|
||||
{
|
||||
if (!TargetingHandle.IsValid())
|
||||
{
|
||||
return;
|
||||
}
|
||||
FTargetingDefaultResultsSet& TargetingResults = FTargetingDefaultResultsSet::FindOrAdd(TargetingHandle);
|
||||
|
||||
if (Hits.Num() > 0)
|
||||
{
|
||||
for (const FHitResult& HitResult : Hits)
|
||||
{
|
||||
if (!HitResult.GetActor())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
bool bAddResult = true;
|
||||
for (const FTargetingDefaultResultData& ResultData : TargetingResults.TargetResults)
|
||||
{
|
||||
if (ResultData.HitResult.GetActor() == HitResult.GetActor())
|
||||
{
|
||||
bAddResult = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (bAddResult)
|
||||
{
|
||||
FTargetingDefaultResultData* ResultData = new(TargetingResults.TargetResults) FTargetingDefaultResultData();
|
||||
ResultData->HitResult = HitResult;
|
||||
}
|
||||
}
|
||||
|
||||
#if ENABLE_DRAW_DEBUG
|
||||
BuildTraceResultsDebugString(TargetingHandle, TargetingResults.TargetResults);
|
||||
#endif // ENABLE_DRAW_DEBUG
|
||||
}
|
||||
else if (bGenerateDefaultHitResult)
|
||||
{
|
||||
// If there were no hits, add a default HitResult at the end of the trace
|
||||
FHitResult HitResult;
|
||||
const FVector Start = (GetSourceLocation(TargetingHandle) + GetSourceOffset(TargetingHandle));
|
||||
const FVector End = Start + (GetTraceDirection(TargetingHandle) * GetTraceLength(TargetingHandle));
|
||||
// Start param could be player ViewPoint. We want HitResult to always display the StartLocation.
|
||||
HitResult.TraceStart = Start;
|
||||
HitResult.TraceEnd = End;
|
||||
HitResult.Location = End;
|
||||
HitResult.ImpactPoint = End;
|
||||
FTargetingDefaultResultData* ResultData = new(TargetingResults.TargetResults) FTargetingDefaultResultData();
|
||||
ResultData->HitResult = HitResult;
|
||||
}
|
||||
}
|
||||
|
||||
void UGCS_TargetingSelectionTask_LineTrace::InitCollisionParams(const FTargetingRequestHandle& TargetingHandle, FCollisionQueryParams& OutParams) const
|
||||
{
|
||||
if (const FTargetingSourceContext* SourceContext = FTargetingSourceContext::Find(TargetingHandle))
|
||||
{
|
||||
if (bIgnoreSourceActor && SourceContext->SourceActor)
|
||||
{
|
||||
OutParams.AddIgnoredActor(SourceContext->SourceActor);
|
||||
}
|
||||
|
||||
if (bIgnoreInstigatorActor && SourceContext->InstigatorActor)
|
||||
{
|
||||
OutParams.AddIgnoredActor(SourceContext->InstigatorActor);
|
||||
}
|
||||
|
||||
TArray<AActor*> AdditionalActorsToIgnoreArray;
|
||||
GetAdditionalActorsToIgnore(TargetingHandle, AdditionalActorsToIgnoreArray);
|
||||
|
||||
if (AdditionalActorsToIgnoreArray.Num() > 0)
|
||||
{
|
||||
OutParams.AddIgnoredActors(AdditionalActorsToIgnoreArray);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if WITH_EDITOR
|
||||
bool UGCS_TargetingSelectionTask_LineTrace::CanEditChange(const FProperty* InProperty) const
|
||||
{
|
||||
bool bCanEdit = Super::CanEditChange(InProperty);
|
||||
|
||||
if (bCanEdit && InProperty)
|
||||
{
|
||||
const FName PropertyName = InProperty->GetFName();
|
||||
|
||||
if (PropertyName == GET_MEMBER_NAME_CHECKED(UGCS_TargetingSelectionTask_LineTrace, TraceChannel))
|
||||
{
|
||||
return (CollisionProfileName.Name == TEXT("NoCollision"));
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
#endif // WITH_EDITOR
|
||||
|
||||
|
||||
#if ENABLE_DRAW_DEBUG
|
||||
void UGCS_TargetingSelectionTask_LineTrace::DrawDebug(UTargetingSubsystem* TargetingSubsystem, FTargetingDebugInfo& Info, const FTargetingRequestHandle& TargetingHandle, float XOffset, float YOffset,
|
||||
int32 MinTextRowsToAdvance) const
|
||||
{
|
||||
#if WITH_EDITORONLY_DATA
|
||||
if (UTargetingSubsystem::IsTargetingDebugEnabled())
|
||||
{
|
||||
FTargetingDebugData& DebugData = FTargetingDebugData::FindOrAdd(TargetingHandle);
|
||||
FString& ScratchPadString = DebugData.DebugScratchPadStrings.FindOrAdd(GetNameSafe(this));
|
||||
if (!ScratchPadString.IsEmpty())
|
||||
{
|
||||
if (Info.Canvas)
|
||||
{
|
||||
Info.Canvas->SetDrawColor(FColor::Yellow);
|
||||
}
|
||||
|
||||
FString TaskString = FString::Printf(TEXT("Results : %s"), *ScratchPadString);
|
||||
TargetingSubsystem->DebugLine(Info, TaskString, XOffset, YOffset, MinTextRowsToAdvance);
|
||||
}
|
||||
}
|
||||
#endif // WITH_EDITORONLY_DATA
|
||||
}
|
||||
|
||||
void UGCS_TargetingSelectionTask_LineTrace::DrawDebugTrace(const FTargetingRequestHandle TargetingHandle, const FVector& StartLocation, const FVector& EndLocation, const bool bHit,
|
||||
const TArray<FHitResult>& Hits) const
|
||||
{
|
||||
if (UTargetingSubsystem::IsTargetingDebugEnabled())
|
||||
{
|
||||
if (UWorld* World = GetSourceContextWorld(TargetingHandle))
|
||||
{
|
||||
const float DrawTime = UTargetingSubsystem::GetOverrideTargetingLifeTime();
|
||||
const EDrawDebugTrace::Type DrawDebugType = DrawTime <= 0.0f ? EDrawDebugTrace::Type::ForOneFrame : EDrawDebugTrace::Type::ForDuration;
|
||||
const FLinearColor TraceColor = FLinearColor::Red;
|
||||
const FLinearColor TraceHitColor = FLinearColor::Green;
|
||||
DrawDebugLineTraceMulti(World, StartLocation, EndLocation, DrawDebugType, bHit, Hits, TraceColor, TraceHitColor, DrawTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UGCS_TargetingSelectionTask_LineTrace::BuildTraceResultsDebugString(const FTargetingRequestHandle& TargetingHandle, const TArray<FTargetingDefaultResultData>& TargetResults) const
|
||||
{
|
||||
#if WITH_EDITORONLY_DATA
|
||||
if (UTargetingSubsystem::IsTargetingDebugEnabled())
|
||||
{
|
||||
FTargetingDebugData& DebugData = FTargetingDebugData::FindOrAdd(TargetingHandle);
|
||||
FString& ScratchPadString = DebugData.DebugScratchPadStrings.FindOrAdd(GetNameSafe(this));
|
||||
|
||||
for (const FTargetingDefaultResultData& TargetData : TargetResults)
|
||||
{
|
||||
if (const AActor* Target = TargetData.HitResult.GetActor())
|
||||
{
|
||||
if (ScratchPadString.IsEmpty())
|
||||
{
|
||||
ScratchPadString = FString::Printf(TEXT("%s"), *GetNameSafe(Target));
|
||||
}
|
||||
else
|
||||
{
|
||||
ScratchPadString += FString::Printf(TEXT(", %s"), *GetNameSafe(Target));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif // WITH_EDITORONLY_DATA
|
||||
}
|
||||
|
||||
void UGCS_TargetingSelectionTask_LineTrace::ResetTraceResultsDebugString(const FTargetingRequestHandle& TargetingHandle) const
|
||||
{
|
||||
#if WITH_EDITORONLY_DATA
|
||||
FTargetingDebugData& DebugData = FTargetingDebugData::FindOrAdd(TargetingHandle);
|
||||
FString& ScratchPadString = DebugData.DebugScratchPadStrings.FindOrAdd(GetNameSafe(this));
|
||||
ScratchPadString.Reset();
|
||||
#endif // WITH_EDITORONLY_DATA
|
||||
}
|
||||
|
||||
#endif // ENABLE_DRAW_DEBUG
|
||||
@@ -0,0 +1,120 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
|
||||
#include "Targeting/Selections/GCS_TargetingSelectionTask_TraceExt.h"
|
||||
|
||||
#include "GCS_LogChannels.h"
|
||||
#include "Collision/DEPRECATED_GCS_CollisionTraceInstance.h"
|
||||
#include "Targeting/GCS_TargetingSourceInterface.h"
|
||||
|
||||
UDEPRECATED_GCS_CollisionTraceInstance* UGCS_TargetingSelectionTask_TraceExt::GetSourceTraceInstance_Implementation(const FTargetingRequestHandle& TargetingHandle) const
|
||||
{
|
||||
if (const FTargetingSourceContext* SourceContext = FTargetingSourceContext::Find(TargetingHandle))
|
||||
{
|
||||
if (SourceContext->SourceObject)
|
||||
{
|
||||
return Cast<UDEPRECATED_GCS_CollisionTraceInstance>(SourceContext->SourceObject);
|
||||
}
|
||||
}
|
||||
UE_LOG(LogGCS, Error, TEXT("No valid CollisionTraceInstance passed in as SourceObject! TargetingPreset:%s"), *GetOuter()->GetName());
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void UGCS_TargetingSelectionTask_TraceExt::Execute(const FTargetingRequestHandle& TargetingHandle) const
|
||||
{
|
||||
Super::Execute(TargetingHandle);
|
||||
}
|
||||
|
||||
FVector UGCS_TargetingSelectionTask_TraceExt::GetSourceLocation_Implementation(const FTargetingRequestHandle& TargetingHandle) const
|
||||
{
|
||||
if (const FTargetingSourceContext* SourceContext = FTargetingSourceContext::Find(TargetingHandle))
|
||||
{
|
||||
if (bUseContextLocationAsSourceLocation)
|
||||
{
|
||||
return SourceContext->SourceLocation;
|
||||
}
|
||||
if (SourceContext->SourceActor)
|
||||
{
|
||||
return SourceContext->SourceActor->GetActorLocation();
|
||||
}
|
||||
|
||||
return SourceContext->SourceLocation;
|
||||
}
|
||||
|
||||
return FVector::ZeroVector;
|
||||
}
|
||||
|
||||
FVector UGCS_TargetingSelectionTask_TraceExt::GetTraceDirection_Implementation(const FTargetingRequestHandle& TargetingHandle) const
|
||||
{
|
||||
if (const FTargetingSourceContext* SourceContext = FTargetingSourceContext::Find(TargetingHandle))
|
||||
{
|
||||
if (SourceContext->SourceObject && SourceContext->SourceObject->GetClass()->ImplementsInterface(UGCS_TargetingSourceInterface::StaticClass()))
|
||||
{
|
||||
FVector TraceDirection;
|
||||
if (IGCS_TargetingSourceInterface::Execute_GetTraceDirection(SourceContext->SourceObject, TraceDirection))
|
||||
{
|
||||
return TraceDirection;
|
||||
}
|
||||
}
|
||||
|
||||
return SourceContext->SourceLocation;
|
||||
}
|
||||
|
||||
return Super::GetTraceDirection_Implementation(TargetingHandle);
|
||||
}
|
||||
|
||||
void UGCS_TargetingSelectionTask_TraceExt::GetAdditionalActorsToIgnore_Implementation(const FTargetingRequestHandle& TargetingHandle, TArray<AActor*>& OutAdditionalActorsToIgnore) const
|
||||
{
|
||||
if (const FTargetingSourceContext* SourceContext = FTargetingSourceContext::Find(TargetingHandle))
|
||||
{
|
||||
if (SourceContext->SourceObject && SourceContext->SourceObject->GetClass()->ImplementsInterface(UGCS_TargetingSourceInterface::StaticClass()))
|
||||
{
|
||||
OutAdditionalActorsToIgnore.Append(IGCS_TargetingSourceInterface::Execute_GetAdditionalActorsToIgnore(SourceContext->SourceObject));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
float UGCS_TargetingSelectionTask_TraceExt::GetTraceLevel_Implementation(const FTargetingRequestHandle& TargetingHandle) const
|
||||
{
|
||||
if (const FTargetingSourceContext* SourceContext = FTargetingSourceContext::Find(TargetingHandle))
|
||||
{
|
||||
if (!IsValid(SourceContext->SourceObject))
|
||||
{
|
||||
UE_LOG(LogGCS, Error, TEXT("No valid Context Source Object found! TargetingPreset:%s"), *GetOuter()->GetName());
|
||||
return 0;
|
||||
}
|
||||
if (!SourceContext->SourceObject->GetClass()->ImplementsInterface(UGCS_TargetingSourceInterface::StaticClass()))
|
||||
{
|
||||
UE_LOG(LogGCS, Error, TEXT("Source Object(%s) doesn't implements GCS_TargetingSourceInterface.! TargetingPreset:%s"),
|
||||
*SourceContext->SourceObject->GetName(), *GetOuter()->GetName());
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
float UGCS_TargetingSelectionTask_TraceExt::GetTraceLength_Implementation(const FTargetingRequestHandle& TargetingHandle) const
|
||||
{
|
||||
return bTraceLengthLevel ? DefaultTraceLength.GetValueAtLevel(GetTraceLevel(TargetingHandle)) : DefaultTraceLength.GetValue();
|
||||
}
|
||||
|
||||
float UGCS_TargetingSelectionTask_TraceExt::GetSweptTraceRadius_Implementation(const FTargetingRequestHandle& TargetingHandle) const
|
||||
{
|
||||
return bSweptTraceRadiusLevel ? DefaultSweptTraceRadius.GetValueAtLevel(GetTraceLevel(TargetingHandle)) : DefaultSweptTraceRadius.GetValue();
|
||||
}
|
||||
|
||||
float UGCS_TargetingSelectionTask_TraceExt::GetSweptTraceCapsuleHalfHeight_Implementation(const FTargetingRequestHandle& TargetingHandle) const
|
||||
{
|
||||
return bSweptTraceCapsuleHalfHeightLevel ? DefaultSweptTraceCapsuleHalfHeight.GetValueAtLevel(GetTraceLevel(TargetingHandle)) : DefaultSweptTraceCapsuleHalfHeight.GetValue();
|
||||
}
|
||||
|
||||
FVector UGCS_TargetingSelectionTask_TraceExt::GetSweptTraceBoxHalfExtents_Implementation(const FTargetingRequestHandle& TargetingHandle) const
|
||||
{
|
||||
if (bSweptTraceBoxHalfExtentLevel)
|
||||
{
|
||||
float Level = GetTraceLevel(TargetingHandle);
|
||||
return FVector(DefaultSweptTraceBoxHalfExtentX.GetValueAtLevel(Level), DefaultSweptTraceBoxHalfExtentY.GetValueAtLevel(Level), DefaultSweptTraceBoxHalfExtentZ.GetValueAtLevel(Level));
|
||||
}
|
||||
|
||||
return Super::GetSweptTraceBoxHalfExtents_Implementation(TargetingHandle);
|
||||
}
|
||||
@@ -0,0 +1,212 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
|
||||
#include "Targeting/Selections/GCS_TargetingSelectionTask_TraceExt_BindShape.h"
|
||||
|
||||
#include "GCS_LogChannels.h"
|
||||
#include "Components/BoxComponent.h"
|
||||
#include "Components/CapsuleComponent.h"
|
||||
#include "Components/SphereComponent.h"
|
||||
#include "Components/ShapeComponent.h"
|
||||
#include "Misc/DataValidation.h"
|
||||
#include "Targeting/GCS_TargetingFunctionLibrary.h"
|
||||
#include "Targeting/GCS_TargetingSourceInterface.h"
|
||||
|
||||
void UGCS_TargetingSelectionTask_TraceExt_BindShape::Execute(const FTargetingRequestHandle& TargetingHandle) const
|
||||
{
|
||||
bool bMatchShapeType = true;
|
||||
UShapeComponent* Shape = GetTraceShape(TargetingHandle);
|
||||
|
||||
if (!IsValid(Shape))
|
||||
{
|
||||
GCS_LOG(Warning, "")
|
||||
bMatchShapeType = false;
|
||||
}
|
||||
if (TraceType == ETargetingTraceType::Box && !Shape->IsA<UBoxComponent>())
|
||||
{
|
||||
GCS_LOG(Warning, "%s: Trace type mismatched! want Box, got %s. %s",
|
||||
*GetNameSafe(GetOuter()),
|
||||
*GetNameSafe(Shape->GetClass()),
|
||||
*UGCS_TargetingFunctionLibrary::GetTargetingSourceContextDebugString(TargetingHandle)
|
||||
)
|
||||
bMatchShapeType = false;
|
||||
}
|
||||
if (TraceType == ETargetingTraceType::Capsule && !Shape->IsA<UCapsuleComponent>())
|
||||
{
|
||||
GCS_LOG(Warning, "%s: Trace type mismatched! want Capsule, got %s. %s",
|
||||
*GetNameSafe(GetOuter()),
|
||||
*GetNameSafe(Shape->GetClass()),
|
||||
*UGCS_TargetingFunctionLibrary::GetTargetingSourceContextDebugString(TargetingHandle)
|
||||
)
|
||||
bMatchShapeType = false;
|
||||
}
|
||||
if (TraceType == ETargetingTraceType::Sphere && !Shape->IsA<USphereComponent>())
|
||||
{
|
||||
GCS_LOG(Warning, "%s: Trace type mismatched! want Capsule, got %s. %s",
|
||||
*GetNameSafe(GetOuter()),
|
||||
*GetNameSafe(Shape->GetClass()),
|
||||
*UGCS_TargetingFunctionLibrary::GetTargetingSourceContextDebugString(TargetingHandle)
|
||||
)
|
||||
bMatchShapeType = false;
|
||||
}
|
||||
|
||||
if (bMatchShapeType)
|
||||
{
|
||||
Super::Execute(TargetingHandle);
|
||||
}
|
||||
else
|
||||
{
|
||||
SetTaskAsyncState(TargetingHandle, ETargetingTaskAsyncState::Completed);
|
||||
}
|
||||
}
|
||||
|
||||
float UGCS_TargetingSelectionTask_TraceExt_BindShape::GetSweptTraceRadius_Implementation(const FTargetingRequestHandle& TargetingHandle) const
|
||||
{
|
||||
float BaseValue = -1;
|
||||
|
||||
if (USphereComponent* Shape = Cast<USphereComponent>(GetTraceShape(TargetingHandle)))
|
||||
{
|
||||
BaseValue = Shape->GetScaledSphereRadius();
|
||||
}
|
||||
if (const UCapsuleComponent* Shape = Cast<UCapsuleComponent>(GetTraceShape(TargetingHandle)))
|
||||
{
|
||||
BaseValue = Shape->GetScaledCapsuleRadius();
|
||||
}
|
||||
|
||||
float Value = Super::GetSweptTraceRadius_Implementation(TargetingHandle);
|
||||
|
||||
if (BaseValue < 0)
|
||||
{
|
||||
return Value;
|
||||
}
|
||||
|
||||
if (SweptTraceRadiusModType == EGCS_TraceDataModifyType::Add)
|
||||
{
|
||||
return BaseValue + Value;
|
||||
}
|
||||
|
||||
if (SweptTraceRadiusModType == EGCS_TraceDataModifyType::Multiply)
|
||||
{
|
||||
return BaseValue * Value;
|
||||
}
|
||||
return BaseValue;
|
||||
}
|
||||
|
||||
float UGCS_TargetingSelectionTask_TraceExt_BindShape::GetSweptTraceCapsuleHalfHeight_Implementation(const FTargetingRequestHandle& TargetingHandle) const
|
||||
{
|
||||
float BaseValue = -1;
|
||||
|
||||
if (const UCapsuleComponent* Capsule = Cast<UCapsuleComponent>(GetTraceShape(TargetingHandle)))
|
||||
{
|
||||
BaseValue = Capsule->GetScaledCapsuleHalfHeight();
|
||||
}
|
||||
|
||||
float Value = Super::GetSweptTraceCapsuleHalfHeight_Implementation(TargetingHandle);
|
||||
|
||||
if (BaseValue < 0)
|
||||
{
|
||||
return Value;
|
||||
}
|
||||
|
||||
if (SweptTraceCapsuleHalfHeightModType == EGCS_TraceDataModifyType::Add)
|
||||
{
|
||||
return BaseValue + Value;
|
||||
}
|
||||
|
||||
if (SweptTraceCapsuleHalfHeightModType == EGCS_TraceDataModifyType::Multiply)
|
||||
{
|
||||
return BaseValue * Value;
|
||||
}
|
||||
return BaseValue;
|
||||
}
|
||||
|
||||
FVector UGCS_TargetingSelectionTask_TraceExt_BindShape::GetSweptTraceBoxHalfExtents_Implementation(const FTargetingRequestHandle& TargetingHandle) const
|
||||
{
|
||||
FVector BaseValue = FVector::ZeroVector;
|
||||
|
||||
if (const UBoxComponent* Shape = Cast<UBoxComponent>(GetTraceShape(TargetingHandle)))
|
||||
{
|
||||
BaseValue = Shape->GetScaledBoxExtent();
|
||||
}
|
||||
|
||||
FVector Value = Super::GetSweptTraceBoxHalfExtents_Implementation(TargetingHandle);
|
||||
|
||||
if (BaseValue == FVector::ZeroVector)
|
||||
{
|
||||
return Value;
|
||||
}
|
||||
|
||||
if (SweptTraceBoxHalfExtentModType == EGCS_TraceDataModifyType::Add)
|
||||
{
|
||||
return BaseValue + Value;
|
||||
}
|
||||
|
||||
if (SweptTraceBoxHalfExtentModType == EGCS_TraceDataModifyType::Multiply)
|
||||
{
|
||||
return BaseValue * Value;
|
||||
}
|
||||
return BaseValue;
|
||||
}
|
||||
|
||||
UShapeComponent* UGCS_TargetingSelectionTask_TraceExt_BindShape::GetTraceShape(const FTargetingRequestHandle& TargetingHandle) const
|
||||
{
|
||||
UShapeComponent* ShapeComponent = nullptr;
|
||||
if (const FTargetingSourceContext* SourceContext = FTargetingSourceContext::Find(TargetingHandle))
|
||||
{
|
||||
if (SourceContext->SourceObject)
|
||||
{
|
||||
if (SourceContext->SourceObject->GetClass()->ImplementsInterface(UGCS_TargetingSourceInterface::StaticClass()))
|
||||
{
|
||||
if (IGCS_TargetingSourceInterface::Execute_GetTraceShape(SourceContext->SourceObject, ShapeComponent))
|
||||
{
|
||||
return ShapeComponent;
|
||||
}
|
||||
UE_LOG(LogGCS, VeryVerbose, TEXT("Source Object(%s) doesn't provide valid ShapeComponent! TargetingPreset:%s"),
|
||||
*SourceContext->SourceObject->GetName(), *GetOuter()->GetName());
|
||||
}
|
||||
else
|
||||
{
|
||||
UE_LOG(LogGCS, VeryVerbose, TEXT("Source Object(%s) doesn't implements GCS_TargetingSourceInterface.! TargetingPreset:%s"),
|
||||
*SourceContext->SourceObject->GetName(), *GetOuter()->GetName());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
UE_LOG(LogGCS, Error, TEXT("No valid Context Source Object found! TargetingPreset:%s"), *GetOuter()->GetName());
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
FRotator UGCS_TargetingSelectionTask_TraceExt_BindShape::GetSweptTraceRotation_Implementation(const FTargetingRequestHandle& TargetingHandle) const
|
||||
{
|
||||
if (const FTargetingSourceContext* SourceContext = FTargetingSourceContext::Find(TargetingHandle))
|
||||
{
|
||||
if (SourceContext->SourceObject && SourceContext->SourceObject->GetClass()->ImplementsInterface(UGCS_TargetingSourceInterface::StaticClass()))
|
||||
{
|
||||
FRotator TraceRotation;
|
||||
if (IGCS_TargetingSourceInterface::Execute_GetSweptTraceRotation(SourceContext->SourceObject, TraceRotation))
|
||||
{
|
||||
return TraceRotation;
|
||||
}
|
||||
}
|
||||
}
|
||||
return Super::GetSweptTraceRotation_Implementation(TargetingHandle);
|
||||
}
|
||||
|
||||
#if WITH_EDITORONLY_DATA
|
||||
EDataValidationResult UGCS_TargetingSelectionTask_TraceExt_BindShape::IsDataValid(class FDataValidationContext& Context) const
|
||||
{
|
||||
if (TraceType == ETargetingTraceType::Line)
|
||||
{
|
||||
FString Txt = FString::Format(TEXT("TraceType == Line is not allowed in this type of task:{0} "), {GetClass()->GetName()});
|
||||
Context.AddError(FText::FromString(Txt));
|
||||
}
|
||||
if (bUseContextLocationAsSourceLocation)
|
||||
{
|
||||
FString Txt = FString::Format(TEXT("bUseContextLocationAsSourceLocation is not allowed in this type of task:{0} "), {GetClass()->GetName()});
|
||||
Context.AddError(FText::FromString(Txt));
|
||||
}
|
||||
return Super::IsDataValid(Context);
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,87 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
#include "Team/GCS_CombatTeamAgentComponent.h"
|
||||
#include "GCS_LogChannels.h"
|
||||
#include "GameFramework/Controller.h"
|
||||
#include "GameFramework/Pawn.h"
|
||||
#include "GenericTeamAgentInterface.h"
|
||||
#include "Net/UnrealNetwork.h"
|
||||
#include "Net/Core/PushModel/PushModel.h"
|
||||
|
||||
// Sets default values for this component's properties
|
||||
UGCS_CombatTeamAgentComponent::UGCS_CombatTeamAgentComponent()
|
||||
{
|
||||
// Set this component to be initialized when the game starts, and to be ticked every frame. You can turn these features
|
||||
// off to improve performance if you don't need them.
|
||||
PrimaryComponentTick.bCanEverTick = false;
|
||||
SetIsReplicatedByDefault(true);
|
||||
// ...
|
||||
}
|
||||
|
||||
void UGCS_CombatTeamAgentComponent::GetLifetimeReplicatedProps(TArray<class FLifetimeProperty>& OutLifetimeProps) const
|
||||
{
|
||||
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
|
||||
|
||||
FDoRepLifetimeParams SharedParams;
|
||||
SharedParams.bIsPushBased = true;
|
||||
|
||||
DOREPLIFETIME_WITH_PARAMS_FAST(ThisClass, CombatTeamId, SharedParams);
|
||||
}
|
||||
|
||||
FGCS_CombatTeamIdChangedSignature* UGCS_CombatTeamAgentComponent::GetOnTeamIdChangedDelegate()
|
||||
{
|
||||
return &OnTeamIdChangedEvent;
|
||||
}
|
||||
|
||||
FGenericTeamId UGCS_CombatTeamAgentComponent::GetCombatTeamId_Implementation() const
|
||||
{
|
||||
return CombatTeamId;
|
||||
}
|
||||
|
||||
void UGCS_CombatTeamAgentComponent::SetCombatTeamId_Implementation(FGenericTeamId NewTeamId)
|
||||
{
|
||||
if (GetOwner()->HasAuthority())
|
||||
{
|
||||
const FGenericTeamId OldTeamID = CombatTeamId;
|
||||
|
||||
MARK_PROPERTY_DIRTY_FROM_NAME(ThisClass, CombatTeamId, this);
|
||||
CombatTeamId = NewTeamId;
|
||||
if (bAssignTeamIdToController)
|
||||
{
|
||||
if (APawn* Pawn = Cast<APawn>(GetOwner()))
|
||||
{
|
||||
if (IGenericTeamAgentInterface* AgentInterface = Cast<IGenericTeamAgentInterface>(Pawn->GetController()))
|
||||
{
|
||||
AgentInterface->SetGenericTeamId(NewTeamId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ConditionalBroadcastTeamChanged(this, OldTeamID, NewTeamId);
|
||||
}
|
||||
else
|
||||
{
|
||||
UE_LOG(LogGCS, Error, TEXT("Cannot set team for %s on non-authority"), *GetPathName(this));
|
||||
}
|
||||
}
|
||||
|
||||
void UGCS_CombatTeamAgentComponent::OnRep_CombatTeamId(FGenericTeamId OldTeamID)
|
||||
{
|
||||
ConditionalBroadcastTeamChanged(this, OldTeamID, CombatTeamId);
|
||||
}
|
||||
|
||||
// Called when the game starts
|
||||
void UGCS_CombatTeamAgentComponent::BeginPlay()
|
||||
{
|
||||
Super::BeginPlay();
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
// Called every frame
|
||||
void UGCS_CombatTeamAgentComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
|
||||
{
|
||||
Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
|
||||
|
||||
// ...
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
|
||||
#include "Team/GCS_CombatTeamAgentInterface.h"
|
||||
|
||||
#include "GCS_LogChannels.h"
|
||||
|
||||
|
||||
// Add default functionality here for any IGCS_CombatTeamAgentInterface functions that are not pure virtual.
|
||||
void IGCS_CombatTeamAgentInterface::SetCombatTeamId_Implementation(FGenericTeamId NewTeamId)
|
||||
{
|
||||
if (IGenericTeamAgentInterface* TeamAgentInterface = Cast<IGenericTeamAgentInterface>(_getUObject()))
|
||||
{
|
||||
TeamAgentInterface->SetGenericTeamId(NewTeamId);
|
||||
}
|
||||
}
|
||||
|
||||
FGenericTeamId IGCS_CombatTeamAgentInterface::GetCombatTeamId_Implementation() const
|
||||
{
|
||||
if (IGenericTeamAgentInterface* TeamAgentInterface = Cast<IGenericTeamAgentInterface>(_getUObject()))
|
||||
{
|
||||
return TeamAgentInterface->GetGenericTeamId();
|
||||
}
|
||||
|
||||
return FGenericTeamId::NoTeam;
|
||||
}
|
||||
|
||||
void IGCS_CombatTeamAgentInterface::ConditionalBroadcastTeamChanged(TScriptInterface<IGCS_CombatTeamAgentInterface> This, FGenericTeamId OldTeamID, FGenericTeamId NewTeamID)
|
||||
{
|
||||
if (OldTeamID != NewTeamID)
|
||||
{
|
||||
UObject* ThisObj = This.GetObject();
|
||||
UE_LOG(LogGCS, Verbose, TEXT("[%s] %s assigned team %d"), *GetClientServerContextString(ThisObj), *GetPathNameSafe(ThisObj), NewTeamID.GetId());
|
||||
This.GetInterface()->GetTeamChangedDelegateChecked().Broadcast(ThisObj, OldTeamID, NewTeamID);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
|
||||
#include "Utility/GCS_AttackDefinitionFunctionLibrary.h"
|
||||
|
||||
#include "CombatFlow/GCS_AttackDefinition.h"
|
||||
|
||||
#if WITH_EDITOR
|
||||
void UGCS_AttackDefinitionFunctionLibrary::MigrateAttackDefinitionTable(UDataTable* InTable)
|
||||
{
|
||||
if (InTable && InTable->GetRowStruct()->IsChildOf(FGCS_AttackDefinition::StaticStruct()))
|
||||
{
|
||||
TArray<FGCS_AttackDefinition*> Rows;
|
||||
InTable->GetAllRows<FGCS_AttackDefinition>(TEXT("Migration"),Rows);
|
||||
|
||||
InTable->MarkPackageDirty();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,473 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
|
||||
#include "Utility/GCS_CombatFunctionLibrary.h"
|
||||
#include "Components/SkeletalMeshComponent.h"
|
||||
#include "GCS_CombatEntityInterface.h"
|
||||
#include "GCS_CombatSystemSettings.h"
|
||||
#include "Team/GCS_CombatTeamAgentInterface.h"
|
||||
#include "GCS_LogChannels.h"
|
||||
#include "GGA_GameplayEffectContext.h"
|
||||
#include "GameFramework/Pawn.h"
|
||||
#include "GameFramework/Controller.h"
|
||||
#include "AbilitySystem/GCS_GameplayEffectContext.h"
|
||||
#include "CombatFlow/GCS_AttackRequest.h"
|
||||
#include "GameFramework/Character.h"
|
||||
#include "Kismet/KismetMathLibrary.h"
|
||||
#include "Utilities/GGA_GameplayEffectFunctionLibrary.h"
|
||||
#include "Weapon/GCS_WeaponInterface.h"
|
||||
|
||||
TScriptInterface<IGCS_CombatTeamAgentInterface> UGCS_CombatFunctionLibrary::GetCombatTeamAgentInterface(AActor* Actor)
|
||||
{
|
||||
if (IsValid(Actor))
|
||||
{
|
||||
if (Actor->GetClass()->ImplementsInterface(UGCS_CombatTeamAgentInterface::StaticClass()))
|
||||
{
|
||||
return Actor;
|
||||
}
|
||||
TArray<UActorComponent*> Components = Actor->GetComponentsByInterface(UGCS_CombatTeamAgentInterface::StaticClass());
|
||||
return Components.IsValidIndex(0) ? Components[0] : nullptr;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool UGCS_CombatFunctionLibrary::FindCombatTeamAgentInterface(AActor* Actor, TScriptInterface<IGCS_CombatTeamAgentInterface>& OutInterface)
|
||||
{
|
||||
OutInterface = GetCombatTeamAgentInterface(Actor);
|
||||
return OutInterface != nullptr;
|
||||
}
|
||||
|
||||
TScriptInterface<IGCS_CombatEntityInterface> UGCS_CombatFunctionLibrary::GetCombatEntityInterface(AActor* Actor)
|
||||
{
|
||||
if (IsValid(Actor))
|
||||
{
|
||||
if (Actor->GetClass()->ImplementsInterface(UGCS_CombatEntityInterface::StaticClass()))
|
||||
{
|
||||
return Actor;
|
||||
}
|
||||
TArray<UActorComponent*> Components = Actor->GetComponentsByInterface(UGCS_CombatEntityInterface::StaticClass());
|
||||
return Components.IsValidIndex(0) ? Components[0] : nullptr;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
UObject* UGCS_CombatFunctionLibrary::GetCombatEntity(AActor* Actor)
|
||||
{
|
||||
if (IsValid(Actor))
|
||||
{
|
||||
if (Actor->GetClass()->ImplementsInterface(UGCS_CombatEntityInterface::StaticClass()))
|
||||
{
|
||||
return Actor;
|
||||
}
|
||||
TArray<UActorComponent*> Components = Actor->GetComponentsByInterface(UGCS_CombatEntityInterface::StaticClass());
|
||||
return Components.IsValidIndex(0) ? Components[0] : nullptr;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
TScriptInterface<IGCS_WeaponInterface> UGCS_CombatFunctionLibrary::GetWeaponInterface(AActor* Actor)
|
||||
{
|
||||
if (IsValid(Actor))
|
||||
{
|
||||
if (Actor->GetClass()->ImplementsInterface(UGCS_WeaponInterface::StaticClass()))
|
||||
{
|
||||
return Actor;
|
||||
}
|
||||
TArray<UActorComponent*> Components = Actor->GetComponentsByInterface(UGCS_WeaponInterface::StaticClass());
|
||||
return Components.IsValidIndex(0) ? Components[0] : nullptr;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
USkeletalMeshComponent* UGCS_CombatFunctionLibrary::GetMainCharacterMeshComponent(AActor* Actor, FName OverrideMeshLookupTag)
|
||||
{
|
||||
if (IsValid(Actor))
|
||||
{
|
||||
if (OverrideMeshLookupTag != NAME_None)
|
||||
{
|
||||
TArray<UActorComponent*> Components = Actor->GetComponentsByTag(USkeletalMeshComponent::StaticClass(), OverrideMeshLookupTag);
|
||||
if (Components.IsValidIndex(0))
|
||||
{
|
||||
return Cast<USkeletalMeshComponent>(Components[0]);
|
||||
}
|
||||
}
|
||||
else if (const UGCS_CombatSystemSettings* Settings = UGCS_CombatSystemSettings::Get())
|
||||
{
|
||||
TArray<UActorComponent*> Components = Actor->GetComponentsByTag(USkeletalMeshComponent::StaticClass(), Settings->CharacterMeshLookupTag);
|
||||
if (Components.IsValidIndex(0))
|
||||
{
|
||||
return Cast<USkeletalMeshComponent>(Components[0]);
|
||||
}
|
||||
}
|
||||
|
||||
if (ACharacter* Char = Cast<ACharacter>(Actor))
|
||||
{
|
||||
return Char->GetMesh();
|
||||
}
|
||||
|
||||
if (USkeletalMeshComponent* Component = Cast<USkeletalMeshComponent>(Actor->GetComponentByClass(USkeletalMeshComponent::StaticClass())))
|
||||
{
|
||||
return Component;
|
||||
}
|
||||
|
||||
UE_LOG(LogGCS, Warning, TEXT("Failed to find main character mesh component on actor class:%s"), *Actor->GetClass()->GetName());
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
UMeshComponent* UGCS_CombatFunctionLibrary::GetMainMeshComponent(AActor* Actor, FName OverrideMeshLookupTag)
|
||||
{
|
||||
if (IsValid(Actor))
|
||||
{
|
||||
if (OverrideMeshLookupTag != NAME_None)
|
||||
{
|
||||
if (UActorComponent* Component = Actor->FindComponentByTag(UMeshComponent::StaticClass(), OverrideMeshLookupTag))
|
||||
{
|
||||
return Cast<UMeshComponent>(Component);
|
||||
}
|
||||
}
|
||||
else if (const UGCS_CombatSystemSettings* Settings = UGCS_CombatSystemSettings::Get())
|
||||
{
|
||||
TArray<UActorComponent*> Components = Actor->GetComponentsByTag(UMeshComponent::StaticClass(), Settings->CharacterMeshLookupTag);
|
||||
if (Components.IsValidIndex(0))
|
||||
{
|
||||
return Cast<UMeshComponent>(Components[0]);
|
||||
}
|
||||
}
|
||||
|
||||
if (UMeshComponent* Component = Cast<UMeshComponent>(Actor->GetComponentByClass(UMeshComponent::StaticClass())))
|
||||
{
|
||||
return Component;
|
||||
}
|
||||
|
||||
UE_LOG(LogGCS, Warning, TEXT("Failed to find main mesh component on actor class:%s"), *Actor->GetClass()->GetName());
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
TArray<FName> UGCS_CombatFunctionLibrary::GetSocketNamesWithPrefix(const USceneComponent* Component, FString Prefix, ESearchCase::Type SearchCase)
|
||||
{
|
||||
if (IsValid(Component))
|
||||
{
|
||||
return Component->GetAllSocketNames().FilterByPredicate([&](const FName& SocketName)
|
||||
{
|
||||
return SocketName.ToString().StartsWith(Prefix, SearchCase);
|
||||
});
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
bool UGCS_CombatFunctionLibrary::FindCombatInterface(AActor* Actor, TScriptInterface<IGCS_CombatEntityInterface>& OutInterface)
|
||||
{
|
||||
OutInterface = GetCombatEntityInterface(Actor);
|
||||
return OutInterface.GetObject() != nullptr;
|
||||
}
|
||||
|
||||
bool UGCS_CombatFunctionLibrary::FindWeaponInterface(AActor* Actor, TScriptInterface<IGCS_WeaponInterface>& OutInterface)
|
||||
{
|
||||
OutInterface = GetWeaponInterface(Actor);
|
||||
return OutInterface.GetObject() != nullptr;
|
||||
}
|
||||
|
||||
FRotator UGCS_CombatFunctionLibrary::CalculateAngleBetweenActors(const AActor* From, const AActor* To)
|
||||
{
|
||||
if (IsValid(From) && IsValid(To))
|
||||
{
|
||||
return UKismetMathLibrary::NormalizedDeltaRotator(UKismetMathLibrary::FindLookAtRotation(From->GetActorLocation(), To->GetActorLocation()), From->GetActorRotation());
|
||||
}
|
||||
|
||||
// TODO Warning.
|
||||
return FRotator::ZeroRotator;
|
||||
}
|
||||
|
||||
bool UGCS_CombatFunctionLibrary::IsSameCombatTeam(const AActor* A, const AActor* B)
|
||||
{
|
||||
return GetCombatTeamId(A) == GetCombatTeamId(B);
|
||||
}
|
||||
|
||||
FGenericTeamId UGCS_CombatFunctionLibrary::GetCombatTeamId(const AActor* Actor)
|
||||
{
|
||||
return QueryCombatTeamId(Actor, true, true);
|
||||
}
|
||||
|
||||
FGenericTeamId UGCS_CombatFunctionLibrary::QueryCombatTeamId(const AActor* Actor, bool bCombatAgent, bool bGenericAgent)
|
||||
{
|
||||
if (!IsValid(Actor))
|
||||
{
|
||||
return FGenericTeamId::NoTeam;
|
||||
}
|
||||
|
||||
if (bCombatAgent)
|
||||
{
|
||||
if (Actor->GetClass()->ImplementsInterface(UGCS_CombatTeamAgentInterface::StaticClass()))
|
||||
{
|
||||
return IGCS_CombatTeamAgentInterface::Execute_GetCombatTeamId(Actor);
|
||||
}
|
||||
|
||||
TArray<UActorComponent*> Components = Actor->GetComponentsByInterface(UGCS_CombatTeamAgentInterface::StaticClass());
|
||||
if (Components.IsValidIndex(0))
|
||||
{
|
||||
return IGCS_CombatTeamAgentInterface::Execute_GetCombatTeamId(Components[0]);
|
||||
}
|
||||
|
||||
if (const APawn* Pawn = Cast<APawn>(Actor))
|
||||
{
|
||||
if (Pawn->GetController() && Pawn->GetController()->GetClass()->ImplementsInterface(UGCS_CombatTeamAgentInterface::StaticClass()))
|
||||
{
|
||||
return IGCS_CombatTeamAgentInterface::Execute_GetCombatTeamId(Pawn->GetController());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (bGenericAgent)
|
||||
{
|
||||
if (const IGenericTeamAgentInterface* GenericTeamAgentInterface = Cast<IGenericTeamAgentInterface>(Actor))
|
||||
{
|
||||
return GenericTeamAgentInterface->GetGenericTeamId();
|
||||
}
|
||||
|
||||
if (const APawn* Pawn = Cast<APawn>(Actor))
|
||||
{
|
||||
if (const IGenericTeamAgentInterface* GenericTeamAgentInterface = Cast<IGenericTeamAgentInterface>(Pawn->GetController()))
|
||||
{
|
||||
return GenericTeamAgentInterface->GetGenericTeamId();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return FGenericTeamId::NoTeam;
|
||||
}
|
||||
|
||||
EGCS_Direction UGCS_CombatFunctionLibrary::CalculateDirectionFromAngle(const float Angle)
|
||||
{
|
||||
if (UKismetMathLibrary::InRange_FloatFloat(Angle, -45.0f, 45.0f))
|
||||
{
|
||||
return EGCS_Direction::Forward;
|
||||
}
|
||||
|
||||
if (UKismetMathLibrary::InRange_FloatFloat(Angle, 45.0f, 135.f))
|
||||
{
|
||||
return EGCS_Direction::Right;
|
||||
}
|
||||
|
||||
if (UKismetMathLibrary::InRange_FloatFloat(Angle, -135.f, -45.f))
|
||||
{
|
||||
return EGCS_Direction::Left;
|
||||
}
|
||||
return EGCS_Direction::Backward;
|
||||
}
|
||||
|
||||
TSoftObjectPtr<UAnimMontage> UGCS_CombatFunctionLibrary::SelectMontageByDirection(EGCS_Direction Direction, TArray<TSoftObjectPtr<UAnimMontage>> Montages)
|
||||
{
|
||||
switch (Direction)
|
||||
{
|
||||
case EGCS_Direction::Forward:
|
||||
return Montages.IsValidIndex(0) ? Montages[0] : nullptr;
|
||||
case EGCS_Direction::Backward:
|
||||
return Montages.IsValidIndex(1) ? Montages[1] : nullptr;
|
||||
case EGCS_Direction::Left:
|
||||
return Montages.IsValidIndex(2) ? Montages[2] : nullptr;
|
||||
case EGCS_Direction::Right:
|
||||
return Montages.IsValidIndex(3) ? Montages[3] : nullptr;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void UGCS_CombatFunctionLibrary::AddTaggedValue(TArray<FGCS_TaggedValue>& TaggedValues, FGameplayTag Tag, float ValueToAdd)
|
||||
{
|
||||
bool bFound = false;
|
||||
for (FGCS_TaggedValue& TaggedValue : TaggedValues)
|
||||
{
|
||||
if (TaggedValue.Attribute == Tag)
|
||||
{
|
||||
TaggedValue.Value += ValueToAdd;
|
||||
bFound = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!bFound)
|
||||
{
|
||||
FGCS_TaggedValue Temp;
|
||||
Temp.Attribute = Tag;
|
||||
Temp.Value = ValueToAdd;
|
||||
TaggedValues.Add(Temp);
|
||||
}
|
||||
}
|
||||
|
||||
float UGCS_CombatFunctionLibrary::GetTaggedValue(const TArray<FGCS_TaggedValue> TaggedValues, FGameplayTag Tag)
|
||||
{
|
||||
for (const FGCS_TaggedValue& TaggedValue : TaggedValues)
|
||||
{
|
||||
if (TaggedValue.Attribute == Tag)
|
||||
{
|
||||
return TaggedValue.Value;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
FGameplayTagContainer UGCS_CombatFunctionLibrary::FilterGameplayTagContainer(const FGameplayTagContainer& TagContainer, FGameplayTagContainer OtherContainer)
|
||||
{
|
||||
return TagContainer.Filter(OtherContainer);
|
||||
}
|
||||
|
||||
FGameplayEffectSpecHandle UGCS_CombatFunctionLibrary::AddAttackHandleToGameplayEffectSpec(FGameplayEffectSpecHandle SpecHandle, FDataTableRowHandle AttackHandle)
|
||||
{
|
||||
if (SpecHandle.IsValid() && !AttackHandle.IsNull())
|
||||
{
|
||||
if (FGCS_AttackDefinition* AtkDef = AttackHandle.GetRow<FGCS_AttackDefinition>(TEXT("AddAttackHandleToGameplayEffectSpec")))
|
||||
{
|
||||
AddAttackDefinitionToGameplayEffectSpec(SpecHandle, *AtkDef);
|
||||
}
|
||||
|
||||
FGameplayEffectContextHandle ContextHandle = SpecHandle.Data->GetEffectContext();
|
||||
EffectContextSetAttackDefinitionHandle(ContextHandle, AttackHandle);
|
||||
}
|
||||
return SpecHandle;
|
||||
}
|
||||
|
||||
FGameplayEffectSpecHandle UGCS_CombatFunctionLibrary::AddAttackDefinitionToGameplayEffectSpec(FGameplayEffectSpecHandle SpecHandle, const FGCS_AttackDefinition& AtkDefinition)
|
||||
{
|
||||
if (SpecHandle.IsValid())
|
||||
{
|
||||
SpecHandle.Data->AppendDynamicAssetTags(AtkDefinition.AttackTags);
|
||||
|
||||
// apply set by callers from atk definition.
|
||||
|
||||
for (const TTuple<FGameplayTag, float>& ByCallerMagnitude : AtkDefinition.SetByCallerMagnitudes)
|
||||
{
|
||||
if (ByCallerMagnitude.Key.IsValid() && ByCallerMagnitude.Value > 0)
|
||||
{
|
||||
SpecHandle.Data->SetSetByCallerMagnitude(ByCallerMagnitude.Key, ByCallerMagnitude.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return SpecHandle;
|
||||
}
|
||||
|
||||
void UGCS_CombatFunctionLibrary::AddAttackHandleToGameplayEffectContainerSpec(FGGA_GameplayEffectContainerSpec ContainerSpec, FDataTableRowHandle AttackHandle)
|
||||
{
|
||||
if (!AttackHandle.IsNull())
|
||||
{
|
||||
if (FGCS_AttackDefinition* AtkDef = AttackHandle.GetRow<FGCS_AttackDefinition>(TEXT("AddAttackHandleToGameplayEffectSpec")))
|
||||
{
|
||||
for (const FGameplayEffectSpecHandle& SpecHandle : ContainerSpec.TargetGameplayEffectSpecs)
|
||||
{
|
||||
AddAttackDefinitionToGameplayEffectSpec(SpecHandle, *AtkDef);
|
||||
FGameplayEffectContextHandle ContextHandle = SpecHandle.Data->GetEffectContext();
|
||||
EffectContextSetAttackDefinitionHandle(ContextHandle, AttackHandle);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UGCS_CombatFunctionLibrary::EffectContextSetAttackDefinitionHandle(FGameplayEffectContextHandle EffectContext, FDataTableRowHandle Handle)
|
||||
{
|
||||
if (FGCS_ContextPayload_Combat* Payload = EffectContextGetMutableCombatPayload(EffectContext))
|
||||
{
|
||||
Payload->AtkDataTable = Handle.DataTable;
|
||||
Payload->AtkRowName = Handle.RowName;
|
||||
}
|
||||
else
|
||||
{
|
||||
UE_LOG(LogGCS, Error, TEXT("Can't access GCS_GameplayEffectContext! You need to setup GCS_AbilitySystemGlobals as AbilitySystemGlobalsClassName."))
|
||||
}
|
||||
}
|
||||
|
||||
FGCS_ContextPayload_Combat UGCS_CombatFunctionLibrary::EffectContextGetCombatPayload(FGameplayEffectContextHandle EffectContext)
|
||||
{
|
||||
if (FGGA_GameplayEffectContext* Context = UGGA_GameplayEffectFunctionLibrary::GetEffectContextPtr(EffectContext))
|
||||
{
|
||||
if (FGCS_ContextPayload_Combat* CombatPayload = Context->FindMutablePayloadByType<FGCS_ContextPayload_Combat>())
|
||||
{
|
||||
return *CombatPayload;
|
||||
}
|
||||
}
|
||||
return FGCS_ContextPayload_Combat();
|
||||
}
|
||||
|
||||
FGCS_ContextPayload_Combat* UGCS_CombatFunctionLibrary::EffectContextGetMutableCombatPayload(const FGameplayEffectContextHandle& EffectContext)
|
||||
{
|
||||
if (FGGA_GameplayEffectContext* Context = UGGA_GameplayEffectFunctionLibrary::GetEffectContextPtr(EffectContext))
|
||||
{
|
||||
if (FGCS_ContextPayload_Combat* CombatPayload = Context->FindOrAddMutablePayloadPtr<FGCS_ContextPayload_Combat>())
|
||||
{
|
||||
return CombatPayload;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void UGCS_CombatFunctionLibrary::EffectContextAddTagToCombatPayload(FGameplayEffectContextHandle EffectContext, FGameplayTag TagToAdd)
|
||||
{
|
||||
if (TagToAdd.IsValid())
|
||||
{
|
||||
if (FGCS_ContextPayload_Combat* Payload = EffectContextGetMutableCombatPayload(EffectContext))
|
||||
{
|
||||
Payload->DynamicTags.AddTagFast(TagToAdd);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UGCS_CombatFunctionLibrary::EffectContextSetTaggedValueToCombatPayload(FGameplayEffectContextHandle EffectContext, FGameplayTag Tag, float NewValue)
|
||||
{
|
||||
if (FGCS_ContextPayload_Combat* Payload = EffectContextGetMutableCombatPayload(EffectContext))
|
||||
{
|
||||
Payload->SetTaggedValue(Tag, NewValue);
|
||||
}
|
||||
}
|
||||
|
||||
float UGCS_CombatFunctionLibrary::EffectContextGetTaggedValueFromCombatPayload(FGameplayEffectContextHandle EffectContext, FGameplayTag Tag)
|
||||
{
|
||||
if (FGCS_ContextPayload_Combat* Payload = EffectContextGetMutableCombatPayload(EffectContext))
|
||||
{
|
||||
return Payload->GetTaggedValue(Tag);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void UGCS_CombatFunctionLibrary::EffectContextGetDynamicTagsFromCombatPayload(FGameplayEffectContextHandle EffectContext, FGameplayTagContainer& OutTags)
|
||||
{
|
||||
if (FGCS_ContextPayload_Combat* Payload = EffectContextGetMutableCombatPayload(EffectContext))
|
||||
{
|
||||
OutTags.Reset();
|
||||
OutTags = Payload->DynamicTags;
|
||||
}
|
||||
}
|
||||
|
||||
FDataTableRowHandle UGCS_CombatFunctionLibrary::EffectContextGetAttackDefinitionHandle(FGameplayEffectContextHandle EffectContext)
|
||||
{
|
||||
if (FGCS_ContextPayload_Combat* Payload = EffectContextGetMutableCombatPayload(EffectContext))
|
||||
{
|
||||
FDataTableRowHandle Handle;
|
||||
Handle.DataTable = Payload->AtkDataTable;
|
||||
Handle.RowName = Payload->AtkRowName;
|
||||
return Handle;
|
||||
}
|
||||
return FDataTableRowHandle();
|
||||
}
|
||||
|
||||
bool UGCS_CombatFunctionLibrary::EffectContextGetIsPredictingContext(FGameplayEffectContextHandle EffectContext)
|
||||
{
|
||||
if (FGCS_ContextPayload_Combat* Payload = EffectContextGetMutableCombatPayload(EffectContext))
|
||||
{
|
||||
return Payload->bIsPredictingContext;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
FGCS_AttackDefinition UGCS_CombatFunctionLibrary::EffectContextGetAttackDefinition(FGameplayEffectContextHandle EffectContext)
|
||||
{
|
||||
FDataTableRowHandle Handle = EffectContextGetAttackDefinitionHandle(EffectContext);
|
||||
if (FGCS_AttackDefinition* Def = Handle.GetRow<FGCS_AttackDefinition>(TEXT("EffectContextGetAttackDefinition")))
|
||||
{
|
||||
return *Def;
|
||||
}
|
||||
return FGCS_AttackDefinition();
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
|
||||
#include "Weapon/GCS_AttachmentRelationshipMapping.h"
|
||||
#include "Animation/Skeleton.h"
|
||||
#include "Engine/StaticMesh.h"
|
||||
#include "Engine/SkeletalMesh.h"
|
||||
#include "Components/SkeletalMeshComponent.h"
|
||||
#include "UObject/ObjectSaveContext.h"
|
||||
|
||||
bool UGCS_AttachmentRelationshipMapping::FindRelationshipForMesh(const USkeletalMeshComponent* InSkeletalMeshComponent, const UStaticMesh* InStaticMesh, const USkeletalMesh* InSkeletalMesh,
|
||||
FName InSocketName, FGCS_AttachmentRelationship& OutRelationship) const
|
||||
{
|
||||
bool bFoundMatchingSkeleton = false;
|
||||
if (bUseNameMatching)
|
||||
{
|
||||
if (!CompatibleSkeletonNames.IsEmpty() && InSkeletalMeshComponent && InSkeletalMeshComponent->GetSkeletalMeshAsset())
|
||||
{
|
||||
for (const FString& SkeletonName : CompatibleSkeletonNames)
|
||||
{
|
||||
if (SkeletonName.IsEmpty())
|
||||
continue;
|
||||
|
||||
if (USkeleton* Skeleton = InSkeletalMeshComponent->GetSkeletalMeshAsset()->GetSkeleton())
|
||||
{
|
||||
if (Skeleton->GetName() == SkeletonName)
|
||||
{
|
||||
bFoundMatchingSkeleton = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!CompatibleSkeletons.IsEmpty() && InSkeletalMeshComponent && InSkeletalMeshComponent->GetSkeletalMeshAsset())
|
||||
{
|
||||
for (TSoftObjectPtr<USkeleton> CompatibleSkeleton : CompatibleSkeletons)
|
||||
{
|
||||
if (CompatibleSkeleton.IsNull())
|
||||
continue;
|
||||
|
||||
if (!CompatibleSkeleton.IsValid())
|
||||
{
|
||||
CompatibleSkeleton.LoadSynchronous();
|
||||
}
|
||||
if (CompatibleSkeleton == InSkeletalMeshComponent->GetSkeletalMeshAsset()->GetSkeleton())
|
||||
{
|
||||
bFoundMatchingSkeleton = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (!bFoundMatchingSkeleton)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const FGCS_AttachmentRelationship& Rel : Relationships)
|
||||
{
|
||||
if (Rel.SocketName != InSocketName)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (IsValid(InStaticMesh) && Rel.StaticMesh.IsValid() && InStaticMesh == Rel.StaticMesh.Get())
|
||||
{
|
||||
OutRelationship = Rel;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (IsValid(InSkeletalMesh) && Rel.SkeletalMesh.IsValid() && InSkeletalMesh == Rel.SkeletalMesh.Get())
|
||||
{
|
||||
OutRelationship = Rel;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
#if WITH_EDITOR
|
||||
|
||||
void UGCS_AttachmentRelationshipMapping::PreSave(FObjectPreSaveContext SaveContext)
|
||||
{
|
||||
for (FGCS_AttachmentRelationship& Relationship : Relationships)
|
||||
{
|
||||
if (Relationship.SocketName == NAME_None)
|
||||
{
|
||||
Relationship.EditorFriendlyName = "Invalid setup";
|
||||
}
|
||||
else
|
||||
{
|
||||
Relationship.EditorFriendlyName = FString::Format(TEXT("Adjust SM({0})/SKM({1}) for Socket({2})"),
|
||||
{
|
||||
Relationship.StaticMesh.IsNull() ? TEXT("None") : Relationship.StaticMesh.LoadSynchronous()->GetName(),
|
||||
Relationship.SkeletalMesh.IsNull() ? TEXT("None") : Relationship.SkeletalMesh.LoadSynchronous()->GetName(),
|
||||
Relationship.SocketName.ToString()
|
||||
});
|
||||
}
|
||||
}
|
||||
Super::PreSave(SaveContext);
|
||||
}
|
||||
|
||||
EDataValidationResult UGCS_AttachmentRelationshipMapping::IsDataValid(class FDataValidationContext& Context) const
|
||||
{
|
||||
return Super::IsDataValid(Context);
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,188 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
|
||||
#include "Weapon/GCS_WeaponActor.h"
|
||||
#include "Components/PrimitiveComponent.h"
|
||||
#include "GameFramework/Pawn.h"
|
||||
#include "GCS_LogChannels.h"
|
||||
#include "Collision/GCS_TraceSystemComponent.h"
|
||||
#include "Misc/DataValidation.h"
|
||||
#include "Net/UnrealNetwork.h"
|
||||
#include "Net/Core/PushModel/PushModel.h"
|
||||
|
||||
AGCS_WeaponActor::AGCS_WeaponActor(const FObjectInitializer& ObjectInitializer)
|
||||
{
|
||||
PrimaryActorTick.bCanEverTick = true;
|
||||
bReplicates = true;
|
||||
bWeaponActive = false;
|
||||
}
|
||||
|
||||
APawn* AGCS_WeaponActor::GetWeaponOwner_Implementation() const
|
||||
{
|
||||
return Cast<APawn>(GetOwner());
|
||||
}
|
||||
|
||||
const FGameplayTagContainer AGCS_WeaponActor::GetWeaponTags_Implementation() const
|
||||
{
|
||||
return WeaponTags;
|
||||
}
|
||||
|
||||
void AGCS_WeaponActor::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
|
||||
{
|
||||
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
|
||||
|
||||
FDoRepLifetimeParams Parameters;
|
||||
Parameters.bIsPushBased = true;
|
||||
|
||||
DOREPLIFETIME_WITH_PARAMS_FAST(ThisClass, bWeaponActive, Parameters)
|
||||
}
|
||||
|
||||
void AGCS_WeaponActor::SetWeaponActive_Implementation(bool bNewActive)
|
||||
{
|
||||
if (GetOwner()->HasAuthority())
|
||||
{
|
||||
const bool prev = bWeaponActive;
|
||||
bWeaponActive = bNewActive;
|
||||
MARK_PROPERTY_DIRTY_FROM_NAME(ThisClass, bWeaponActive, this);
|
||||
OnWeaponActiveStateChanged(prev);
|
||||
}
|
||||
}
|
||||
|
||||
bool AGCS_WeaponActor::IsWeaponActive_Implementation() const
|
||||
{
|
||||
return bWeaponActive;
|
||||
}
|
||||
|
||||
UPrimitiveComponent* AGCS_WeaponActor::GetPrimitiveComponent_Implementation() const
|
||||
{
|
||||
if (WeaponMeshTagName.IsValid())
|
||||
{
|
||||
UPrimitiveComponent* Primitive = Cast<UPrimitiveComponent>(GetOwner()->FindComponentByTag(UPrimitiveComponent::StaticClass(), WeaponMeshTagName));
|
||||
if (!IsValid(Primitive))
|
||||
{
|
||||
GCS_CLOG(Warning, "failed to find weapon mesh via tag (%s) as weapon primitive component. weapon owner:%s",
|
||||
*WeaponMeshTagName.ToString(),
|
||||
*GetOwner()->GetName());
|
||||
return nullptr;
|
||||
}
|
||||
return Primitive;
|
||||
}
|
||||
GCS_CLOG(Warning, "no weapon primitive component provided. weapon owner:%s", *GetOwner()->GetName());
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void AGCS_WeaponActor::GetOwnedGameplayTags(FGameplayTagContainer& TagContainer) const
|
||||
{
|
||||
TagContainer = Execute_GetWeaponTags(this);
|
||||
}
|
||||
|
||||
void AGCS_WeaponActor::OnWeaponActiveStateChanged_Implementation(bool Prev)
|
||||
{
|
||||
OnWeaponActiveStateChangedEvent.Broadcast(bWeaponActive);
|
||||
}
|
||||
|
||||
void AGCS_WeaponActor::BeginPlay()
|
||||
{
|
||||
Super::BeginPlay();
|
||||
}
|
||||
|
||||
void AGCS_WeaponActor::EndPlay(const EEndPlayReason::Type EndPlayReason)
|
||||
{
|
||||
if (Execute_IsWeaponActive(this))
|
||||
{
|
||||
bWeaponActive = false;
|
||||
RefreshTraceInstance();
|
||||
}
|
||||
Super::EndPlay(EndPlayReason);
|
||||
}
|
||||
|
||||
void AGCS_WeaponActor::RefreshTraceInstance_Implementation()
|
||||
{
|
||||
APawn* Pawn = Execute_GetWeaponOwner(this);
|
||||
if (!IsValid(Pawn))
|
||||
{
|
||||
GCS_CLOG(Warning, "mising weapon owner!")
|
||||
return;
|
||||
}
|
||||
UGCS_TraceSystemComponent* TSC = UGCS_TraceSystemComponent::GetTraceSystemComponent(Pawn);
|
||||
if (!IsValid(TSC))
|
||||
{
|
||||
GCS_CLOG(Warning, "missing trace system on weapon owner(%s)!", *GetNameSafe(Pawn))
|
||||
return;
|
||||
}
|
||||
if (bWeaponActive)
|
||||
{
|
||||
TraceHandles.Reset();
|
||||
for (const FGCS_TraceDefinition& TraceDefinition : TraceDefinitions)
|
||||
{
|
||||
UPrimitiveComponent* Primitive = GetSourceComponentForTrace(TraceDefinition.TraceTag);
|
||||
if (Primitive == nullptr)
|
||||
{
|
||||
GCS_CLOG(Error, "No SourceComponent provided for trace:%s,check your GetSourceComponentForTrace implementation.", *TraceDefinition.TraceTag.ToString());
|
||||
continue;
|
||||
}
|
||||
FGCS_TraceHandle Handle = TSC->AddTrace(TraceDefinition, Primitive, GetSourceObjectForTrace());
|
||||
if (Handle.IsValidHandle())
|
||||
{
|
||||
TraceHandles.Add(Handle);
|
||||
}
|
||||
}
|
||||
TSC->OnTraceHitEvent.AddDynamic(this, &ThisClass::OnAnyTraceHit);
|
||||
TSC->OnTraceStateChangedEvent.AddDynamic(this, &ThisClass::OnAnyTraceStateChanged);
|
||||
}
|
||||
else
|
||||
{
|
||||
TSC->OnTraceHitEvent.RemoveDynamic(this, &ThisClass::OnAnyTraceHit);
|
||||
TSC->OnTraceStateChangedEvent.RemoveDynamic(this, &ThisClass::OnAnyTraceStateChanged);
|
||||
for (const FGCS_TraceHandle& TraceHandle : TraceHandles)
|
||||
{
|
||||
TSC->RemoveTrace(TraceHandle);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
UObject* AGCS_WeaponActor::GetSourceObjectForTrace_Implementation()
|
||||
{
|
||||
return this;
|
||||
}
|
||||
|
||||
UPrimitiveComponent* AGCS_WeaponActor::GetSourceComponentForTrace_Implementation(const FGameplayTag& TraceTag) const
|
||||
{
|
||||
// Default Using weapon primitive as source component for trace
|
||||
if (UPrimitiveComponent* PrimitiveComponent = Execute_GetPrimitiveComponent(this))
|
||||
{
|
||||
return PrimitiveComponent;
|
||||
}
|
||||
|
||||
GCS_CLOG(Error, "Weapon(%s) didn't return return valid primitive component to be used as SourceComponent for traces, try implement this function to get properly SourceComponent For Trace.",
|
||||
*GetNameSafe(this))
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void AGCS_WeaponActor::OnAnyTraceHit_Implementation(const FGCS_TraceHandle& TraceHandle, const FHitResult& HitResult)
|
||||
{
|
||||
}
|
||||
|
||||
void AGCS_WeaponActor::OnAnyTraceStateChanged_Implementation(const FGCS_TraceHandle& TraceHandle, bool NewState)
|
||||
{
|
||||
}
|
||||
|
||||
void AGCS_WeaponActor::Tick(float DeltaSeconds)
|
||||
{
|
||||
Super::Tick(DeltaSeconds);
|
||||
}
|
||||
|
||||
#if WITH_EDITOR
|
||||
EDataValidationResult AGCS_WeaponActor::IsDataValid(class FDataValidationContext& Context) const
|
||||
{
|
||||
for (int32 i = 0; i < TraceDefinitions.Num(); i++)
|
||||
{
|
||||
if (!TraceDefinitions[i].IsValidDefinition())
|
||||
{
|
||||
Context.AddWarning(FText::FromString(FString::Format(TEXT("Found invalid trace definition at index({0})"), {i})));
|
||||
return EDataValidationResult::Invalid;
|
||||
}
|
||||
}
|
||||
return Super::IsDataValid(Context);
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,12 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
|
||||
#include "Weapon/GCS_WeaponInterface.h"
|
||||
#include "GameFramework/Actor.h"
|
||||
#include "GameFramework/Pawn.h"
|
||||
|
||||
// Add default functionality here for any IGCS_WeaponInterface functions that are not pure virtual.
|
||||
UPrimitiveComponent* IGCS_WeaponInterface::GetPrimitiveComponent_Implementation() const
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "GGA_GameplayAbility.h"
|
||||
#include "GCS_CombatAbility.generated.h"
|
||||
|
||||
class IGCS_CombatEntityInterface;
|
||||
class UGCS_CombatSystemComponent;
|
||||
|
||||
/**
|
||||
* Base combat ability.
|
||||
* 基础战斗能力。
|
||||
*/
|
||||
UCLASS(Abstract)
|
||||
class GENERICCOMBATSYSTEM_API UGCS_CombatAbility : public UGGA_GameplayAbility
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
protected:
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, Category="GCS|Ability")
|
||||
UGCS_CombatSystemComponent* GetCombatSystemFromActorInfo() const;
|
||||
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, Category="GCS|Ability")
|
||||
UObject* GetCombatEntityFromActorInfo() const;
|
||||
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, Category="GCS|Ability")
|
||||
TScriptInterface<IGCS_CombatEntityInterface> GetCombatEntityInterfaceFromActorInfo() const;
|
||||
};
|
||||
@@ -0,0 +1,105 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "GCS_CombatAbility.h"
|
||||
#include "Combo/GCS_ComboDefinition.h"
|
||||
#include "GCS_ComboAbility.generated.h"
|
||||
|
||||
class UGCS_CombatSystemComponent;
|
||||
class UAS_Combat;
|
||||
|
||||
/**
|
||||
* This combo ability acted as manager of sub abilities.
|
||||
*/
|
||||
UCLASS(Abstract, HideCategories=(Cooldowns,Input,GampelayEffects))
|
||||
class GENERICCOMBATSYSTEM_API UGCS_ComboAbility : public UGCS_CombatAbility
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
UGCS_ComboAbility();
|
||||
|
||||
virtual void PreActivate(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo,
|
||||
FOnGameplayAbilityEnded::FDelegate* OnGameplayAbilityEndedDelegate, const FGameplayEventData* TriggerEventData = nullptr) override;
|
||||
virtual void ActivateAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo,
|
||||
const FGameplayEventData* TriggerEventData) override;
|
||||
virtual bool CanActivateAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayTagContainer* SourceTags = nullptr,
|
||||
const FGameplayTagContainer* TargetTags = nullptr, FGameplayTagContainer* OptionalRelevantTags = nullptr) const override;
|
||||
virtual void OnGiveAbility(const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilitySpec& Spec) override;
|
||||
virtual void OnRemoveAbility(const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilitySpec& Spec) override;
|
||||
|
||||
virtual void EndAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, bool bReplicateEndAbility,
|
||||
bool bWasCancelled) override;
|
||||
|
||||
protected:
|
||||
UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category = "Combat Ability")
|
||||
bool AllowAdvanceCombo() const;
|
||||
|
||||
UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category = "Combat Ability")
|
||||
void StartCombo(const FGameplayEventData& ComboEvent);
|
||||
virtual void StartCombo_Implementation(const FGameplayEventData& ComboEvent);
|
||||
|
||||
/**
|
||||
* Advance combo with ComboEventData as context.
|
||||
* @param ComboEventData The data used as combo context. 游戏事件数据作为连击上下文。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category = "Combat Ability")
|
||||
void AdvanceCombo(const FGameplayEventData& ComboEventData);
|
||||
virtual void AdvanceCombo_Implementation(const FGameplayEventData& ComboEventData);
|
||||
|
||||
UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category = "Combat Ability")
|
||||
void ResetCombo();
|
||||
|
||||
UFUNCTION()
|
||||
virtual void HandleAbilityEnd(const FAbilityEndedData& AbilityEndedData);
|
||||
|
||||
virtual bool SelectComboDefinition(const FGameplayEventData& ComboEventData, int32 CurrentStep, FGCS_ComboDefinition& OutDefinition);
|
||||
|
||||
/**
|
||||
* This is where you can use the extension filed within your combo definition to apply additional rules.
|
||||
* 这里你可以使用连击定义中的自定义字段来添加额外的选择规则。
|
||||
* @param ComboEvent The combo event data to provide as context.连击事件数据,用作上下文参考。
|
||||
* @param CurrentStep The current combo step of combat system. 当前的连击步骤。
|
||||
* @param ComboDefinition The combo definition you are checking. 正在检查的连击定义。
|
||||
*/
|
||||
UFUNCTION(BlueprintNativeEvent, Category = "Combat Ability")
|
||||
bool CanSelectedComboDefinition(const FGameplayEventData& ComboEvent, int32 CurrentStep, const FGCS_ComboDefinition& ComboDefinition) const;
|
||||
|
||||
virtual void HandleComboExecution(const FGameplayEventData& ComboEventData);
|
||||
|
||||
// virtual void GiveSubAbilities(const FGameplayAbilitySpec& CurrentSpec);
|
||||
// virtual void RemoveSubAbilities();
|
||||
|
||||
protected:
|
||||
bool bCurrentAbilityEnded = false;
|
||||
|
||||
//The ability current combo step was executing.
|
||||
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="Combo")
|
||||
FGameplayAbilitySpecHandle CurrentAbility;
|
||||
|
||||
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="Combo")
|
||||
TSubclassOf<UGameplayAbility> CurrentAbilityClass;
|
||||
|
||||
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="Combo")
|
||||
FGameplayAbilitySpecHandle NextAbility;
|
||||
|
||||
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="Combo")
|
||||
TSubclassOf<UGameplayAbility> NextAbilityAbilityClass;
|
||||
|
||||
int32 DesiredComboStep{INDEX_NONE};
|
||||
|
||||
/**
|
||||
* Granted potential combo abilities.
|
||||
* 赋予的潜在ComboAbilities.
|
||||
*/
|
||||
// UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="Combo")
|
||||
// TArray<FGameplayAbilitySpecHandle> AvailableAbilities;
|
||||
|
||||
FDelegateHandle AbilityEndedDelegateHandle;
|
||||
|
||||
#if WITH_EDITOR
|
||||
virtual EDataValidationResult IsDataValid(class FDataValidationContext& Context) const override;
|
||||
#endif
|
||||
};
|
||||
@@ -0,0 +1,124 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "AttributeSet.h"
|
||||
#include "AbilitySystemComponent.h"
|
||||
#include "NativeGameplayTags.h"
|
||||
|
||||
#include "AS_Poise.generated.h"
|
||||
|
||||
namespace AS_Poise
|
||||
{
|
||||
|
||||
GENERICCOMBATSYSTEM_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(Poise)
|
||||
|
||||
GENERICCOMBATSYSTEM_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(MaxPoise)
|
||||
|
||||
GENERICCOMBATSYSTEM_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(PoiseRecover)
|
||||
|
||||
|
||||
}
|
||||
|
||||
#define ATTRIBUTE_ACCESSORS(ClassName, PropertyName) \
|
||||
GAMEPLAYATTRIBUTE_PROPERTY_GETTER(ClassName, PropertyName) \
|
||||
GAMEPLAYATTRIBUTE_VALUE_GETTER(PropertyName) \
|
||||
GAMEPLAYATTRIBUTE_VALUE_SETTER(PropertyName) \
|
||||
GAMEPLAYATTRIBUTE_VALUE_INITTER(PropertyName)
|
||||
|
||||
UCLASS()
|
||||
class GENERICCOMBATSYSTEM_API UAS_Poise : public UAttributeSet
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
|
||||
public:
|
||||
|
||||
UAS_Poise();
|
||||
|
||||
virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override;
|
||||
|
||||
virtual void PreAttributeChange(const FGameplayAttribute& Attribute, float& NewValue) override;
|
||||
|
||||
virtual void PostAttributeChange(const FGameplayAttribute& Attribute, float OldValue, float NewValue) override;
|
||||
|
||||
virtual bool PreGameplayEffectExecute(struct FGameplayEffectModCallbackData& Data) override;
|
||||
|
||||
virtual void PostGameplayEffectExecute(const FGameplayEffectModCallbackData& Data) override;
|
||||
|
||||
// Current Poise value of an actor.(actor的当前抗打击值)
|
||||
UPROPERTY(BlueprintReadOnly, ReplicatedUsing = OnRep_Poise, Category = "Attribute|PoiseSet", Meta = (AllowPrivateAccess = true))
|
||||
FGameplayAttributeData Poise{ 3 };
|
||||
ATTRIBUTE_ACCESSORS(ThisClass, Poise)
|
||||
|
||||
// Max Poise value of an actor.(actor的最大抗打击值)
|
||||
UPROPERTY(BlueprintReadOnly, ReplicatedUsing = OnRep_MaxPoise, Category = "Attribute|PoiseSet", Meta = (AllowPrivateAccess = true))
|
||||
FGameplayAttributeData MaxPoise{ 3 };
|
||||
ATTRIBUTE_ACCESSORS(ThisClass, MaxPoise)
|
||||
|
||||
// How many Poise to recover per second.(每秒恢复抗打击值)
|
||||
UPROPERTY(BlueprintReadOnly, ReplicatedUsing = OnRep_PoiseRecover, Category = "Attribute|PoiseSet", Meta = (AllowPrivateAccess = true))
|
||||
FGameplayAttributeData PoiseRecover{ 1 };
|
||||
ATTRIBUTE_ACCESSORS(ThisClass, PoiseRecover)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
UFUNCTION(BlueprintCallable,BlueprintPure,meta=(DisplayName="GetPoiseAttribute"), Category = "Attribute|PoiseSet")
|
||||
static FGameplayAttribute Bp_GetPoiseAttribute();
|
||||
|
||||
UFUNCTION(BlueprintPure,meta=(DisplayName="GetPoise"), Category = "Attribute|PoiseSet")
|
||||
float Bp_GetPoise() const;
|
||||
|
||||
UFUNCTION(BlueprintCallable,meta=(DisplayName="SetPoise"), Category = "Attribute|PoiseSet")
|
||||
void Bp_SetPoise(float NewValue);
|
||||
|
||||
UFUNCTION(BlueprintCallable,meta=(DisplayName="InitPoise"), Category = "Attribute|PoiseSet")
|
||||
void Bp_InitPoise(float NewValue);
|
||||
|
||||
|
||||
UFUNCTION(BlueprintCallable,BlueprintPure,meta=(DisplayName="GetMaxPoiseAttribute"), Category = "Attribute|PoiseSet")
|
||||
static FGameplayAttribute Bp_GetMaxPoiseAttribute();
|
||||
|
||||
UFUNCTION(BlueprintPure,meta=(DisplayName="GetMaxPoise"), Category = "Attribute|PoiseSet")
|
||||
float Bp_GetMaxPoise() const;
|
||||
|
||||
UFUNCTION(BlueprintCallable,meta=(DisplayName="SetMaxPoise"), Category = "Attribute|PoiseSet")
|
||||
void Bp_SetMaxPoise(float NewValue);
|
||||
|
||||
UFUNCTION(BlueprintCallable,meta=(DisplayName="InitMaxPoise"), Category = "Attribute|PoiseSet")
|
||||
void Bp_InitMaxPoise(float NewValue);
|
||||
|
||||
|
||||
UFUNCTION(BlueprintCallable,BlueprintPure,meta=(DisplayName="GetPoiseRecoverAttribute"), Category = "Attribute|PoiseSet")
|
||||
static FGameplayAttribute Bp_GetPoiseRecoverAttribute();
|
||||
|
||||
UFUNCTION(BlueprintPure,meta=(DisplayName="GetPoiseRecover"), Category = "Attribute|PoiseSet")
|
||||
float Bp_GetPoiseRecover() const;
|
||||
|
||||
UFUNCTION(BlueprintCallable,meta=(DisplayName="SetPoiseRecover"), Category = "Attribute|PoiseSet")
|
||||
void Bp_SetPoiseRecover(float NewValue);
|
||||
|
||||
UFUNCTION(BlueprintCallable,meta=(DisplayName="InitPoiseRecover"), Category = "Attribute|PoiseSet")
|
||||
void Bp_InitPoiseRecover(float NewValue);
|
||||
|
||||
|
||||
|
||||
|
||||
protected:
|
||||
|
||||
/** Helper function to proportionally adjust the value of an attribute when it's associated max attribute changes. (i.e. When MaxHealth increases, Health increases by an amount that maintains the same percentage as before) */
|
||||
virtual void AdjustAttributeForMaxChange(FGameplayAttributeData& AffectedAttribute, const FGameplayAttributeData& MaxAttribute, float NewMaxValue, const FGameplayAttribute& AffectedAttributeProperty);
|
||||
|
||||
UFUNCTION()
|
||||
virtual void OnRep_Poise(const FGameplayAttributeData& OldValue);
|
||||
|
||||
UFUNCTION()
|
||||
virtual void OnRep_MaxPoise(const FGameplayAttributeData& OldValue);
|
||||
|
||||
UFUNCTION()
|
||||
virtual void OnRep_PoiseRecover(const FGameplayAttributeData& OldValue);
|
||||
|
||||
};
|
||||
@@ -0,0 +1,16 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "GGA_AbilitySystemGlobals.h"
|
||||
#include "DEPRECATED_GCS_AbilitySystemGlobals.generated.h"
|
||||
|
||||
UCLASS(Deprecated, meta=(DeprecationMessage="GCS_AbilitySystemGlobals is deprecated. Please use GGA_AbilitySystemGlobals instead."))
|
||||
class GENERICCOMBATSYSTEM_API UDEPRECATED_GCS_AbilitySystemGlobals : public UGGA_AbilitySystemGlobals
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
virtual FGameplayEffectContext* AllocGameplayEffectContext() const override;
|
||||
};
|
||||
@@ -0,0 +1,24 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "GameplayEffectComponent.h"
|
||||
#include "GCS_GEComponent_PredictivelyExecute.generated.h"
|
||||
|
||||
/**
|
||||
* This component will predictively execute instant GE which treated as infinite one.
|
||||
* @attention Internally will mark this effect context as local predicting context, so you can conditional apply logic in subsequent codes.
|
||||
*/
|
||||
UCLASS()
|
||||
class GENERICCOMBATSYSTEM_API UGCS_GEComponent_PredictivelyExecute : public UGameplayEffectComponent
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
virtual void OnGameplayEffectApplied(FActiveGameplayEffectsContainer& ActiveGEContainer, FGameplayEffectSpec& GESpec, FPredictionKey& PredictionKey) const override;
|
||||
|
||||
private:
|
||||
UPROPERTY(EditDefaultsOnly, Category = GCS)
|
||||
bool bPredictGameplayCues{false};
|
||||
};
|
||||
@@ -0,0 +1,74 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "GCS_CombatStructLibrary.h"
|
||||
#include "UObject/Object.h"
|
||||
#include "GCS_GameplayEffectContext.generated.h"
|
||||
|
||||
/**
|
||||
* The Combat related data which was carried and pass around within gameplay effect context, as one of the gameplay effect context payload.
|
||||
* 战斗相关数据,在游戏效果上下文中被携带和传递,作为游戏效果上下文数据之一。
|
||||
*/
|
||||
USTRUCT(BlueprintType)
|
||||
struct GENERICCOMBATSYSTEM_API FGCS_ContextPayload_Combat
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
|
||||
void SetTaggedValue(const FGameplayTag& Tag, float NewValue);
|
||||
|
||||
float GetTaggedValue(const FGameplayTag& Tag) const;
|
||||
|
||||
/**
|
||||
* Indicate the effect spec owning this context was Predictively executed.
|
||||
* 表示拥有此上下文的效果实例是以客户端预测方式执行的。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GCS")
|
||||
bool bIsPredictingContext{false};
|
||||
|
||||
UPROPERTY()
|
||||
FPredictionKey PredictionKey;
|
||||
|
||||
/**
|
||||
* Attack definition data table.
|
||||
* 攻击定义数据表。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GCS", meta=(RequiredAssetDataTags = "RowStructure=/Script/GenericCombatSystem.GCS_AttackDefinition"))
|
||||
TObjectPtr<const UDataTable> AtkDataTable{nullptr};
|
||||
|
||||
/**
|
||||
* Row name in the attack definition table.
|
||||
* 攻击定义表中的行名。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GCS")
|
||||
FName AtkRowName{NAME_None};
|
||||
|
||||
/**
|
||||
* Bullet definition data table.
|
||||
* 子弹定义数据表。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GCS", meta=(RequiredAssetDataTags = "RowStructure=/Script/GenericCombatSystem.GCS_BulletDefinition"))
|
||||
TObjectPtr<const UDataTable> BulletDataTable{nullptr};
|
||||
|
||||
/**
|
||||
* Row name in the bullet definition table.
|
||||
* 子弹定义表中的行名。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GCS")
|
||||
FName BulletRowName{NAME_None};
|
||||
|
||||
/**
|
||||
* The tags added during gameplay effect apply(coming from MMC,or Execution).
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GCS")
|
||||
FGameplayTagContainer DynamicTags;
|
||||
|
||||
/**
|
||||
* Array of tagged values associated with the combat process.
|
||||
* 与战斗过程关联的标记值数组。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GCS")
|
||||
TArray<FGCS_TaggedValue> TaggedValues;
|
||||
};
|
||||
@@ -0,0 +1,116 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Abilities/Tasks/AbilityTask.h"
|
||||
#include "Collision/GCS_TraceStructLibrary.h"
|
||||
#include "Components/SkeletalMeshComponent.h"
|
||||
#include "GCS_AbilityTask_CollisionTrace.generated.h"
|
||||
|
||||
class UGCS_AttackRequest_Melee;
|
||||
|
||||
/**
|
||||
* Ability task for handling collision traces in combat.
|
||||
* 处理战斗中碰撞检测的能力任务。
|
||||
*/
|
||||
UCLASS()
|
||||
class GENERICCOMBATSYSTEM_API UGCS_AbilityTask_CollisionTrace : public UAbilityTask
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
UGCS_AbilityTask_CollisionTrace();
|
||||
/**
|
||||
* Creates and activates a collision trace task.
|
||||
* 创建并激活碰撞检测任务。
|
||||
* @param OwningAbility The owning gameplay ability. 所属游戏能力。
|
||||
* @param TaskInstanceName The name of the task instance. 任务实例名称。
|
||||
* @param bAdjustVisibilityBasedAnimTickOption Whether to adjust visibility-based animation ticking. 是否调整基于可见性的动画tick。
|
||||
* @return The created task. 创建的任务。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "GCS|AbilityTasks", meta = (HidePin = "OwningAbility", DefaultToSelf = "OwningAbility", BlueprintInternalUseOnly = "TRUE"))
|
||||
static UGCS_AbilityTask_CollisionTrace* HandleCollisionTraces(UGameplayAbility* OwningAbility, FName TaskInstanceName, bool bAdjustVisibilityBasedAnimTickOption = false);
|
||||
|
||||
/**
|
||||
* Activates the task.
|
||||
* 激活任务。
|
||||
*/
|
||||
virtual void Activate() override;
|
||||
|
||||
/**
|
||||
* Called when the task is destroyed.
|
||||
* 任务销毁时调用。
|
||||
* @param bInOwnerFinished Whether the owner finished the task. 拥有者是否完成了任务。
|
||||
*/
|
||||
virtual void OnDestroy(bool bInOwnerFinished) override;
|
||||
|
||||
/**
|
||||
* Adds a melee attack request to the task.
|
||||
* 向任务添加近战攻击请求。
|
||||
* @param Request The melee attack request. 近战攻击请求。
|
||||
* @param SourceObject Optional source object. 可选的源对象。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "GCS|AbilityTasks")
|
||||
void AddMeleeRequest(const UGCS_AttackRequest_Melee* Request, UObject* SourceObject);
|
||||
|
||||
/**
|
||||
* Removes a melee attack request from the task.
|
||||
* 从任务移除近战攻击请求。
|
||||
* @param Request The melee attack request. 近战攻击请求。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "GCS|AbilityTasks")
|
||||
void RemoveMeleeRequest(const UGCS_AttackRequest_Melee* Request);
|
||||
|
||||
/**
|
||||
* Delegate for trace instance hit events.
|
||||
* 碰撞检测实例命中事件的委托。
|
||||
*/
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_ThreeParams(FGCS_OnTraceInstanceHitSignature, const UGCS_AttackRequest_Melee*, MeleeRequest, const FGCS_TraceHandle&, TraceHandle,
|
||||
const FHitResult&,
|
||||
HitResult);
|
||||
|
||||
/**
|
||||
* Fired when a trace instance detects targets.
|
||||
* 当碰撞检测实例检测到目标时触发。
|
||||
*/
|
||||
UPROPERTY(BlueprintAssignable)
|
||||
FGCS_OnTraceInstanceHitSignature OnTargetsFound;
|
||||
|
||||
protected:
|
||||
/**
|
||||
* Handles trace instance hit events.
|
||||
* 处理碰撞检测实例命中事件。
|
||||
* @param TraceHandle The trace instance. 碰撞检测实例。
|
||||
* @param HitResult The hit result. 命中结果。
|
||||
*/
|
||||
UFUNCTION()
|
||||
void TraceHitCallback(const FGCS_TraceHandle& TraceHandle, const FHitResult& HitResult);
|
||||
|
||||
/**
|
||||
* Map of melee requests to their associated trace instances.
|
||||
* 近战请求及其关联碰撞检测实例的映射。
|
||||
*/
|
||||
TMap<TObjectPtr<const UGCS_AttackRequest_Melee>, TArray<FGCS_TraceHandle>> MeleeRequests;
|
||||
|
||||
/**
|
||||
* Whether to adjust visibility-based animation ticking.
|
||||
* 是否调整基于可见性的动画tick。
|
||||
*/
|
||||
UPROPERTY()
|
||||
bool bAdjustAnimTickOption{false};
|
||||
|
||||
/**
|
||||
* Whether the animation tick option was adjusted.
|
||||
* 是否已调整动画tick选项。
|
||||
*/
|
||||
UPROPERTY()
|
||||
bool bAdjustedAnimTickOption{false};
|
||||
|
||||
/**
|
||||
* The previous animation tick option.
|
||||
* 之前的动画tick选项。
|
||||
*/
|
||||
UPROPERTY()
|
||||
EVisibilityBasedAnimTickOption PrevAnimTickOption{EVisibilityBasedAnimTickOption::AlwaysTickPose};
|
||||
};
|
||||
@@ -0,0 +1,82 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "GameplayTagContainer.h"
|
||||
#include "GCS_BulletStructLibrary.h"
|
||||
#include "Net/Serialization/FastArraySerializer.h"
|
||||
#include "UObject/Object.h"
|
||||
#include "GCS_BulletContainer.generated.h"
|
||||
|
||||
|
||||
struct FGCS_BulletContainer;
|
||||
class UGCS_BulletSystemComponent;
|
||||
|
||||
/**
|
||||
* Structure representing an equipment entry in the container.
|
||||
* 表示容器中装备条目的结构体。
|
||||
* @note WIP
|
||||
*/
|
||||
USTRUCT(BlueprintType)
|
||||
struct GENERICCOMBATSYSTEM_API FGCS_BulletEntry : public FFastArraySerializerItem
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
friend FGCS_BulletContainer;
|
||||
|
||||
//The request id of this entry.
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GCS")
|
||||
FGuid Id;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GCS")
|
||||
FGCS_BulletSpawnParameters SpawnParameters;
|
||||
};
|
||||
|
||||
/**
|
||||
* Container for a list of applied equipment.
|
||||
* 存储已应用装备列表的容器。
|
||||
*/
|
||||
USTRUCT()
|
||||
struct GENERICCOMBATSYSTEM_API FGCS_BulletContainer : public FFastArraySerializer
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
FGCS_BulletContainer()
|
||||
: OwningComponent(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
FGCS_BulletContainer(UGCS_BulletSystemComponent* InComponent)
|
||||
: OwningComponent(InComponent)
|
||||
{
|
||||
}
|
||||
|
||||
void PreReplicatedRemove(const TArrayView<int32> RemovedIndices, int32 FinalSize);
|
||||
|
||||
|
||||
void PostReplicatedAdd(const TArrayView<int32> AddedIndices, int32 FinalSize);
|
||||
|
||||
void PostReplicatedChange(const TArrayView<int32> ChangedIndices, int32 FinalSize);
|
||||
//~End of FFastArraySerializer contract
|
||||
|
||||
bool NetDeltaSerialize(FNetDeltaSerializeInfo& DeltaParms)
|
||||
{
|
||||
return FastArrayDeltaSerialize<FGCS_BulletEntry, FGCS_BulletContainer>(Entries, DeltaParms, *this);
|
||||
}
|
||||
|
||||
|
||||
int32 IndexOfById(const FGuid& Id) const;
|
||||
|
||||
UPROPERTY(VisibleAnywhere, Category="BulletSystem", meta=(ShowOnlyInnerProperties, DisplayName="Bullets"))
|
||||
TArray<FGCS_BulletEntry> Entries;
|
||||
|
||||
UPROPERTY()
|
||||
TObjectPtr<UGCS_BulletSystemComponent> OwningComponent;
|
||||
};
|
||||
|
||||
template <>
|
||||
struct TStructOpsTypeTraits<FGCS_BulletContainer> : TStructOpsTypeTraitsBase2<FGCS_BulletContainer>
|
||||
{
|
||||
enum { WithNetDeltaSerializer = true };
|
||||
};
|
||||
@@ -0,0 +1,406 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "GCS_BulletStructLibrary.h"
|
||||
#include "GCS_EffectCauserInterface.h"
|
||||
#include "GameFramework/Actor.h"
|
||||
#include "GCS_BulletInstance.generated.h"
|
||||
|
||||
class UGCS_AttackRequest_Bullet;
|
||||
class UGCS_BulletSubsystem;
|
||||
class UProjectileMovementComponent;
|
||||
|
||||
/**
|
||||
* Base class for bullet instances.
|
||||
* 子弹实例的基类。
|
||||
*/
|
||||
UCLASS(Abstract, BlueprintType, NotBlueprintable)
|
||||
class GENERICCOMBATSYSTEM_API AGCS_BulletInstance : public AActor, public IGCS_EffectCauserInterface
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
friend UGCS_BulletSubsystem;
|
||||
|
||||
public:
|
||||
/**
|
||||
* Default constructor.
|
||||
* 默认构造函数。
|
||||
*/
|
||||
AGCS_BulletInstance();
|
||||
|
||||
/**
|
||||
* Gets lifetime replicated properties.
|
||||
* 获取生命周期复制属性。
|
||||
* @param OutLifetimeProps The lifetime properties. 生命周期属性。
|
||||
*/
|
||||
virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override;
|
||||
|
||||
/**
|
||||
* Gets the projectile movement component.
|
||||
* 获取子弹的运动组件。
|
||||
* @return The projectile movement component. 运动组件。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, Category="GCS|Bullet")
|
||||
UProjectileMovementComponent* GetProjectileMovementComponent() const;
|
||||
|
||||
/**
|
||||
* Sets the bullet definition handle.
|
||||
* 设置子弹定义句柄。
|
||||
* @param NewHandle The new definition handle. 新定义句柄。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category="GCS|Bullet")
|
||||
void SetDefinitionHandle(UPARAM(meta=(RowType="/Script/GenericCombatSystem.GCS_BulletDefinition")) FDataTableRowHandle NewHandle);
|
||||
|
||||
/**
|
||||
* Sets the bullet's unique ID.
|
||||
* 设置子弹的唯一ID。
|
||||
* @param NewId The new ID. 新ID。
|
||||
*/
|
||||
void SetBulletId(const FGuid& NewId);
|
||||
|
||||
/**
|
||||
* Gets the bullet's unique ID.
|
||||
* 获取子弹的唯一ID。
|
||||
* @return The bullet ID. 子弹ID。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, Category="GCS|Bullet")
|
||||
FGuid GetBulletId() const;
|
||||
|
||||
/**
|
||||
* Sets the parent bullet ID for bullet chains.
|
||||
* 设置子弹链的父子弹ID。
|
||||
* @param NewParentId The parent bullet ID. 父子弹ID。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category="GCS|Bullet")
|
||||
void SetParentBulletId(FGuid NewParentId);
|
||||
|
||||
/**
|
||||
* Gets the parent bullet ID.
|
||||
* 获取父子弹ID。
|
||||
* @return The parent bullet ID. 父子弹ID。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, Category="GCS|Bullet")
|
||||
FGuid GetParentBulletId() const;
|
||||
|
||||
/**
|
||||
* Launches the bullet.
|
||||
* 发射子弹。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category="GCS|Bullet")
|
||||
void LaunchBullet();
|
||||
|
||||
/**
|
||||
* Sets the hit result for the bullet.
|
||||
* 设置子弹的命中结果。
|
||||
* @param NewHitResult The hit result. 命中结果。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category="GCS|Bullet", meta=(DisplayName="Set Bullet HitResult"))
|
||||
void SetHitResult(const FHitResult& NewHitResult);
|
||||
|
||||
/**
|
||||
* Gets the latest hit result.
|
||||
* 获取最新的命中结果。
|
||||
* @return The hit result. 命中结果。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, Category="GCS|Bullet", meta=(DisplayName="Get Bullet HitResult"))
|
||||
const FHitResult& GetHitResult() const;
|
||||
|
||||
/**
|
||||
* Checks if the bullet has gameplay authority.
|
||||
* 检查子弹是否具有游戏权限。
|
||||
* @return True if the bullet has authority. 如果子弹具有权限返回true。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, Category="GCS|Bullet")
|
||||
bool HasGameplayAuthority() const;
|
||||
|
||||
/**
|
||||
* Called after network initialization.
|
||||
* 网络初始化后调用。
|
||||
*/
|
||||
virtual void PostNetInit() override;
|
||||
|
||||
/**
|
||||
* Called after receiving network data.
|
||||
* 接收网络数据后调用。
|
||||
*/
|
||||
virtual void PostNetReceive() override;
|
||||
|
||||
/**
|
||||
* Handles locally predicted bullet instances.
|
||||
* 处理本地预测的子弹实例。
|
||||
* @param PredictedBullet The predicted bullet instance. 预测的子弹实例。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category="GCS|Bullet")
|
||||
void FoundLocalPredictedBullet(AGCS_BulletInstance* PredictedBullet);
|
||||
|
||||
// Effect causer interface
|
||||
/**
|
||||
* Gets the gameplay effect spec handle.
|
||||
* 获取游戏效果规格句柄。
|
||||
* @param OutHandle The effect spec handle (output). 效果规格句柄(输出)。
|
||||
* @return True if successful. 如果成功返回true。
|
||||
*/
|
||||
virtual bool GetEffectSpecHandle_Implementation(FGameplayEffectSpecHandle& OutHandle) override;
|
||||
|
||||
/**
|
||||
* Gets the gameplay effect container.
|
||||
* 获取游戏效果容器。
|
||||
* @return The effect container. 效果容器。
|
||||
*/
|
||||
virtual FGGA_GameplayEffectContainer GetEffectContainer_Implementation() const override;
|
||||
|
||||
/**
|
||||
* Gets the effect container level override.
|
||||
* 获取效果容器级别覆盖。
|
||||
* @return The level override. 级别覆盖。
|
||||
*/
|
||||
virtual int32 GetEffectContainerLevelOverride_Implementation() const override;
|
||||
|
||||
/**
|
||||
* Sets the effect container spec.
|
||||
* 设置效果容器规格。
|
||||
* @param InEffectContainerSpec The effect container spec. 效果容器规格。
|
||||
*/
|
||||
virtual void SetEffectContainerSpec_Implementation(const FGGA_GameplayEffectContainerSpec& InEffectContainerSpec) override;
|
||||
|
||||
/**
|
||||
* Gets the effect container spec.
|
||||
* 获取效果容器规格。
|
||||
* @return The effect container spec. 效果容器规格。
|
||||
*/
|
||||
virtual FGGA_GameplayEffectContainerSpec GetEffectContainerSpec_Implementation() const override;
|
||||
|
||||
/**
|
||||
* Gets the effect class.
|
||||
* 获取效果类。
|
||||
* @return The effect class. 效果类。
|
||||
*/
|
||||
virtual TSubclassOf<UGameplayEffect> GetEffectClass_Implementation() const override;
|
||||
|
||||
/**
|
||||
* Gets the effect level.
|
||||
* 获取效果级别。
|
||||
* @return The effect level. 效果级别。
|
||||
*/
|
||||
virtual int32 GetEffectLevel_Implementation() const override;
|
||||
|
||||
/**
|
||||
* Sets the effect spec.
|
||||
* 设置效果规格。
|
||||
* @param InEffectSpec The effect spec. 效果规格。
|
||||
*/
|
||||
virtual void SetEffectSpec_Implementation(FGameplayEffectSpecHandle& InEffectSpec) override;
|
||||
|
||||
/**
|
||||
* Gets the bullet's shape component.
|
||||
* 获取子弹的形状组件。
|
||||
* @return The shape component. 形状组件。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, BlueprintNativeEvent, Category="GCS|Bullet")
|
||||
UShapeComponent* GetBulletShape() const;
|
||||
virtual UShapeComponent* GetBulletShape_Implementation() const;
|
||||
|
||||
protected:
|
||||
/**
|
||||
* Called when the game starts or when spawned.
|
||||
* 游戏开始或生成时调用。
|
||||
*/
|
||||
virtual void BeginPlay() override;
|
||||
|
||||
/**
|
||||
* Called when the game ends or the bullet is destroyed.
|
||||
* 游戏结束或子弹销毁时调用。
|
||||
* @param EndPlayReason The reason for ending. 结束原因。
|
||||
*/
|
||||
virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override;
|
||||
|
||||
/**
|
||||
* Called when the bullet is taken from the pool with a valid definition.
|
||||
* 子弹从池中取出且具有有效定义时调用。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category="GCS|Bullet")
|
||||
void OnBulletBeginPlay();
|
||||
|
||||
/**
|
||||
* Called when the bullet is returned to the pool and deactivated.
|
||||
* 子弹返回池中并停用时调用。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category="GCS|Bullet")
|
||||
void OnBulletEndPlay();
|
||||
|
||||
/**
|
||||
* Records the initial location and rotation of the bullet.
|
||||
* 记录子弹的初始位置和旋转。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category="GCS|Bullet")
|
||||
void SetupInitialLocationAndRotation();
|
||||
|
||||
/**
|
||||
* Refreshes the bullet's travel states.
|
||||
* 刷新子弹的移动状态。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category="GCS|Bullet")
|
||||
virtual void RefreshTravelStates();
|
||||
|
||||
/**
|
||||
* Checks if the hit result should be penetrated.
|
||||
* 检查命中结果是否应穿透。
|
||||
* @param InHitResult The hit result. 命中结果。
|
||||
* @return True if the hit should be penetrated. 如果命中应穿透返回true。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, Category="GCS|Bullet")
|
||||
virtual bool ShouldPenetrateHitResult(const FHitResult& InHitResult) const;
|
||||
|
||||
/**
|
||||
* Checks if a bullet chain should be generated.
|
||||
* 检查是否应生成子弹链。
|
||||
* @return True if a bullet chain should be generated. 如果应生成子弹链返回true。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, BlueprintNativeEvent, Category="GCS|Bullet")
|
||||
bool ShouldGenerateBullet();
|
||||
|
||||
/**
|
||||
* Handles bullet chain logic on hit.
|
||||
* 处理命中时的子弹链逻辑。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category="GCS|Bullet")
|
||||
void HandleBulletHitChains();
|
||||
|
||||
/**
|
||||
* Applies gameplay effects to the hit result.
|
||||
* 对命中结果应用游戏效果。
|
||||
* @param HitResult The hit result. 命中结果。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category="GCS|Bullet")
|
||||
void ApplyGameplayEffects(FHitResult HitResult);
|
||||
|
||||
/**
|
||||
* Called when the bullet ID is replicated.
|
||||
* 子弹ID复制时调用。
|
||||
* @param Prev The previous bullet ID. 之前的子弹ID。
|
||||
*/
|
||||
UFUNCTION()
|
||||
virtual void OnRep_BulletId(FGuid Prev);
|
||||
|
||||
/**
|
||||
* Called when the bullet definition is replicated.
|
||||
* 子弹定义复制时调用。
|
||||
*/
|
||||
UFUNCTION()
|
||||
void OnRep_BulletDefinition();
|
||||
|
||||
/**
|
||||
* Unique ID of the bullet.
|
||||
* 子弹的唯一ID。
|
||||
*/
|
||||
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, ReplicatedUsing=OnRep_BulletId, Category="GCS|BulletState")
|
||||
FGuid BulletId;
|
||||
|
||||
/**
|
||||
* Whether the bullet is locally predicted.
|
||||
* 子弹是否为本地预测。
|
||||
*/
|
||||
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="GCS|BulletState")
|
||||
bool bIsLocalPredicting{false};
|
||||
|
||||
/**
|
||||
* Whether the bullet was spawned on the server.
|
||||
* 子弹是否在服务器上生成。
|
||||
*/
|
||||
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="GCS|BulletState")
|
||||
bool bServerInitiated{false};
|
||||
|
||||
/**
|
||||
* Parent bullet ID for bullet chains.
|
||||
* 子弹链的父子弹ID。
|
||||
*/
|
||||
UPROPERTY(VisibleAnywhere, Category="GCS|BulletState")
|
||||
FGuid ParentBulletId;
|
||||
|
||||
/**
|
||||
* The associated attack request.
|
||||
* 关联的攻击请求。
|
||||
*/
|
||||
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="GCS|BulletState")
|
||||
TObjectPtr<const UGCS_AttackRequest_Bullet> Request;
|
||||
|
||||
/**
|
||||
* Handle to the bullet definition.
|
||||
* 子弹定义的句柄。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GCS|Bullet", ReplicatedUsing=OnRep_BulletDefinition, meta=(ExposeOnSpawn))
|
||||
FDataTableRowHandle DefinitionHandle;
|
||||
|
||||
/**
|
||||
* Loaded bullet definition.
|
||||
* 加载的子弹定义。
|
||||
*/
|
||||
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="GCS|BulletState")
|
||||
FGCS_BulletDefinition Definition;
|
||||
|
||||
/**
|
||||
* The projectile movement component.
|
||||
* 子弹的运动组件。
|
||||
*/
|
||||
UPROPERTY(VisibleAnywhere, Category="GCS|Bullet")
|
||||
TObjectPtr<UProjectileMovementComponent> ProjectileMovement;
|
||||
|
||||
/**
|
||||
* The gameplay effect spec carried by the bullet.
|
||||
* 子弹携带的游戏效果规格。
|
||||
*/
|
||||
UPROPERTY(VisibleAnywhere, Category="GCS|BulletState")
|
||||
FGameplayEffectSpecHandle EffectSpecHandle;
|
||||
|
||||
/**
|
||||
* The gameplay effect container spec carried by the bullet.
|
||||
* 子弹携带的游戏效果容器规格。
|
||||
*/
|
||||
UPROPERTY(VisibleAnywhere, Category = "GCS|BulletState")
|
||||
FGGA_GameplayEffectContainerSpec EffectContainerSpec;
|
||||
|
||||
/**
|
||||
* Index for bullet chains.
|
||||
* 子弹链的索引。
|
||||
*/
|
||||
UPROPERTY()
|
||||
int32 ChainIndex{0};
|
||||
|
||||
/**
|
||||
* Initial location of the bullet.
|
||||
* 子弹的初始位置。
|
||||
*/
|
||||
UPROPERTY(BlueprintReadWrite, EditDefaultsOnly, Category="GCS|BulletState")
|
||||
FVector InitialActorLocation;
|
||||
|
||||
/**
|
||||
* Initial rotation of the bullet.
|
||||
* 子弹的初始旋转。
|
||||
*/
|
||||
UPROPERTY(BlueprintReadWrite, EditDefaultsOnly, Category="GCS|BulletState")
|
||||
FRotator InitialActorRotation;
|
||||
|
||||
/**
|
||||
* The latest hit result.
|
||||
* 最新的命中结果。
|
||||
*/
|
||||
UPROPERTY(VisibleAnywhere, Category="GCS|BulletState")
|
||||
FHitResult LastHitResult;
|
||||
|
||||
/**
|
||||
* Distance traveled by the bullet.
|
||||
* 子弹的移动距离。
|
||||
*/
|
||||
UPROPERTY(BlueprintReadWrite, VisibleAnywhere, Category="GCS|BulletState")
|
||||
double TraveledDistance{0.0f};
|
||||
|
||||
public:
|
||||
/**
|
||||
* Called every frame.
|
||||
* 每帧调用。
|
||||
* @param DeltaTime Time since last frame. 上一帧以来的时间。
|
||||
*/
|
||||
virtual void Tick(float DeltaTime) override;
|
||||
};
|
||||
@@ -0,0 +1,351 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "GCS_CombatStructLibrary.h"
|
||||
#include "Runtime/Launch/Resources/Version.h"
|
||||
#if ENGINE_MINOR_VERSION < 5
|
||||
#include "InstancedStruct.h"
|
||||
#else
|
||||
#include "StructUtils/InstancedStruct.h"
|
||||
#endif
|
||||
#include "Collision/GCS_TraceStructLibrary.h"
|
||||
#include "UObject/Object.h"
|
||||
#include "GCS_BulletStructLibrary.generated.h"
|
||||
|
||||
class UGCS_AttackRequest_Bullet;
|
||||
class UNiagaraSystem;
|
||||
class AGCS_BulletInstance;
|
||||
|
||||
|
||||
/**
|
||||
* Base struct allow you to extend the bullet definition's fields using C++.
|
||||
* 基础结构体,允许你通过C++拓展子弹定义的字段。
|
||||
*/
|
||||
USTRUCT(BlueprintType, meta=(Hidden))
|
||||
struct GENERICCOMBATSYSTEM_API FGCS_BulletDefinitionExtension
|
||||
{
|
||||
GENERATED_BODY()
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Data structure defining bullet properties and behavior.
|
||||
* 定义子弹属性和行为的数据结构。
|
||||
*/
|
||||
USTRUCT(BlueprintType, meta=(DisplayName="GCS Bullet Definition"))
|
||||
struct GENERICCOMBATSYSTEM_API FGCS_BulletDefinition : public FTableRowBase
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
/**
|
||||
* The bullet actor class.
|
||||
* 子弹Actor类。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Common", meta=(AllowAbstract="false"))
|
||||
TSoftClassPtr<AGCS_BulletInstance> BulletActorClass;
|
||||
|
||||
/**
|
||||
* Duration for which the bullet exists (-1 for infinite).
|
||||
* 子弹存在的持续时间(-1表示无限)。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Common")
|
||||
float Duration{3.0};
|
||||
|
||||
/**
|
||||
* Number of bullets fired at once.
|
||||
* 一次性发射的子弹数量。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Launch Configuration", meta=(ClampMin=1, UIMin=1))
|
||||
int32 BulletCount{1};
|
||||
|
||||
/**
|
||||
* Yaw angle for bullet launch.
|
||||
* 子弹发射的水平角。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Launch Configuration")
|
||||
float LaunchAngle{0.0f};
|
||||
|
||||
/**
|
||||
* Yaw angle interval between bullets.
|
||||
* 子弹之间的水平角间隔。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Launch Configuration")
|
||||
float LaunchAngleInterval{10.0f};
|
||||
|
||||
/**
|
||||
* Pitch angle for bullet launch.
|
||||
* 子弹发射的仰角。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Launch Configuration")
|
||||
float LaunchElevationAngle{0.0f};
|
||||
|
||||
/**
|
||||
* Distance at which bullet attenuation begins.
|
||||
* 子弹开始衰减的距离。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Movement", meta=(Units="cm"))
|
||||
float AttenuationRange{800.0f};
|
||||
|
||||
/**
|
||||
* Gravity scale within the attenuation range.
|
||||
* 衰减范围内的重力系数。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Movement")
|
||||
float GravityScaleInRange{1.0f};
|
||||
|
||||
/**
|
||||
* Gravity scale outside the attenuation range.
|
||||
* 衰减范围外的重力系数。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Movement")
|
||||
float GravityScaleOutRage{1.0f};
|
||||
|
||||
/**
|
||||
* Initial hit radius for the bullet.
|
||||
* 子弹的初始命中半径。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Movement", meta=(Units="cm"))
|
||||
float InitialHitRadius{20.0f};
|
||||
|
||||
/**
|
||||
* Final hit radius for the bullet (-1 to use initial radius).
|
||||
* 子弹的最终命中半径(-1使用初始半径)。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Movement", meta=(Units="cm"))
|
||||
float FinalHitRadius{-1.0f};
|
||||
|
||||
/**
|
||||
* Initial speed of the bullet.
|
||||
* 子弹的初始速度。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Movement", meta=(Units="cm"))
|
||||
float InitialSpeed{1500.0f};
|
||||
|
||||
/**
|
||||
* Minimum speed of the bullet.
|
||||
* 子弹的最小速度。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Movement", meta=(Units="cm"))
|
||||
float MinSpeed{1500.0f};
|
||||
|
||||
/**
|
||||
* Maximum speed of the bullet.
|
||||
* 子弹的最大速度。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Movement", meta=(Units="cm"))
|
||||
float MaxSpeed{1500.0f};
|
||||
|
||||
/**
|
||||
* Handle to the attack definition for the bullet.
|
||||
* 子弹的攻击定义句柄。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Attack", meta=(RowType="/Script/GenericCombatSystem.GCS_AttackDefinition"))
|
||||
FDataTableRowHandle AttackDefinition;
|
||||
|
||||
/**
|
||||
* Trace definitions for hit detection.
|
||||
* 用于命中检测的碰撞检测定义。
|
||||
* @note Overrides trace definitions in the bullet instance class.
|
||||
* @注意 覆盖子弹实例类中的碰撞检测定义。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Trace")
|
||||
TArray<FGCS_TraceDefinition> TraceDefinitions;
|
||||
|
||||
/**
|
||||
* Visual effect for the bullet projectile (Niagara).
|
||||
* 子弹的视觉效果(Niagara)。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="VFX")
|
||||
TSoftObjectPtr<UNiagaraSystem> ProjectileFX;
|
||||
|
||||
/**
|
||||
* Visual effect for the bullet projectile (Cascade).
|
||||
* 子弹的视觉效果(Cascade)。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="VFX")
|
||||
TSoftObjectPtr<UParticleSystem> ProjectileFX_Cascade;
|
||||
|
||||
/**
|
||||
* Visual effect for bullet impact (Niagara).
|
||||
* 子弹命中的视觉效果(Niagara)。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="VFX")
|
||||
TSoftObjectPtr<UNiagaraSystem> ImpactFX;
|
||||
|
||||
/**
|
||||
* Visual effect for bullet impact (Cascade).
|
||||
* 子弹命中的视觉效果(Cascade)。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="VFX")
|
||||
TSoftObjectPtr<UParticleSystem> ImpactFX_Cascade;
|
||||
|
||||
/**
|
||||
* Sound effect for bullet impact.
|
||||
* 子弹命中的音效。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="SFX")
|
||||
TSoftObjectPtr<USoundBase> ImpactSFX;
|
||||
|
||||
/**
|
||||
* Sound effect attached to the bullet projectile.
|
||||
* 附着在子弹上的音效。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="SFX")
|
||||
TSoftObjectPtr<USoundBase> ProjectileSFX;
|
||||
|
||||
/**
|
||||
* Sound effect played once when the bullet spawns.
|
||||
* 子弹生成时播放一次的音效。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="SFX")
|
||||
TSoftObjectPtr<USoundBase> SpawnSFX;
|
||||
|
||||
/**
|
||||
* Whether the bullet penetrates characters/pawns.
|
||||
* 子弹是否穿透角色/Pawn。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Penetration")
|
||||
bool bPenetrateCharacter{false};
|
||||
|
||||
/**
|
||||
* Whether the bullet penetrates map geometry.
|
||||
* 子弹是否穿透地图几何体。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Penetration")
|
||||
bool bPenetrateMap{false};
|
||||
|
||||
/**
|
||||
* Handle to the bullet definition to spawn on hit/expiration.
|
||||
* 命中或失效时生成的子弹定义句柄。
|
||||
* @note Cannot be the same as this bullet.
|
||||
* @注意 不能与此子弹相同。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Hit Configuration", meta=(RowType="/Script/GenericCombatSystem.GCS_BulletDefinition"))
|
||||
FDataTableRowHandle HitBulletDefinition;
|
||||
|
||||
/**
|
||||
* Condition for launching bullet chains.
|
||||
* 子弹链的发射条件。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Hit Configuration", meta=(Categories="GGF.Combat.Bullet.LaunchCond"))
|
||||
FGameplayTag LaunchCondition{FGameplayTag::EmptyTag};
|
||||
|
||||
/**
|
||||
* Native Instanced struct for extending the bullet definition.
|
||||
* 实例化结构体用于扩充子弹定义的字段。
|
||||
* @attention For C++ users only. 仅针对C++用户。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Extension")
|
||||
TInstancedStruct<FGCS_BulletDefinitionExtension> NativeExtension;
|
||||
|
||||
/**
|
||||
* Blueprint Instanced struct for extending the bullet definition.
|
||||
* 实例化结构体用于扩充子弹定义的字段。
|
||||
* @attention For blueprint users only. 仅针对蓝图用户。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Extension")
|
||||
FInstancedStruct Extension;
|
||||
|
||||
/**
|
||||
* Custom user settings for extending the bullet definition.
|
||||
* 扩展子弹定义的自定义用户设置。
|
||||
*/
|
||||
UE_DEPRECATED(1.5, "Using extension field to add custom fields!")
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Deprecated", meta=(ForceInlineRow, BaseStruct = "/Script/GenericCombatSystem.GCS_UserSetting"))
|
||||
TMap<FGameplayTag, FInstancedStruct> UserSettings;
|
||||
|
||||
/**
|
||||
* Shares the hit history to sub bullet.(prevent repeat hit for whole bullet chains.)
|
||||
*/
|
||||
// UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Hit Configuration",meta=(Categories="GGF.Combat.Bullet.LaunchCond"))
|
||||
// bool bUseSharedHitList{true};
|
||||
|
||||
/**
|
||||
* The amount of time between a bullet hits something and when it explodes.
|
||||
*/
|
||||
// UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Hit Configuration", meta=(Units="s", ClampMin=0))
|
||||
// float ExplosionDelay{0.0};
|
||||
|
||||
// Emitter will be added in next version.
|
||||
|
||||
// UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Emitter", meta=(RowType="/Script/GenericCombatSystem.GCS_BulletDefinition"))
|
||||
// FDataTableRowHandle EmitterBulletDefinition;
|
||||
//
|
||||
// UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Emitter")
|
||||
// float EmitterInitialWaitTime{0};
|
||||
//
|
||||
// UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Emitter")
|
||||
// float EmitterMinShootInterval{0};
|
||||
//
|
||||
// UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Emitter")
|
||||
// float EmitterMaxShootInterval{0};
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Parameters for spawning bullets.
|
||||
* 子弹生成参数。
|
||||
*/
|
||||
USTRUCT(BlueprintType)
|
||||
struct GENERICCOMBATSYSTEM_API FGCS_BulletSpawnParameters
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
/**
|
||||
* The owner of the bullet.
|
||||
* 子弹的拥有者。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GCS")
|
||||
TObjectPtr<AActor> Owner{nullptr};
|
||||
|
||||
/**
|
||||
* Handle to the bullet definition.
|
||||
* 子弹定义的句柄。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GCS", meta=(RowType="/Script/GenericCombatSystem.GCS_BulletDefinition"))
|
||||
FDataTableRowHandle DefinitionHandle;
|
||||
|
||||
/**
|
||||
* Transform for spawning the bullet.
|
||||
* 子弹生成时的变换。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GCS")
|
||||
FTransform SpawnTransform{FTransform::Identity};
|
||||
|
||||
/**
|
||||
* The associated attack request.
|
||||
* 关联的攻击请求。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GCS")
|
||||
TObjectPtr<const UGCS_AttackRequest_Bullet> Request{nullptr};
|
||||
|
||||
/**
|
||||
* Whether the bullet is locally predicted.
|
||||
* 子弹是否为本地预测。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GCS")
|
||||
bool bIsLocalPredicting{false};
|
||||
|
||||
/**
|
||||
* IDs for locally predicted bullets.
|
||||
* 本地预测子弹的ID。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GCS", meta=(DisplayName="Local Predicting Bullet Ids"))
|
||||
TArray<FGuid> OverrideBulletIds;
|
||||
|
||||
/**
|
||||
* ID of the parent bullet (for bullet chains).
|
||||
* 父子弹的ID(用于子弹链)。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GCS")
|
||||
FGuid ParentId;
|
||||
|
||||
/**
|
||||
* Returns a debug string representation.
|
||||
* 返回调试字符串表示。
|
||||
* @return The debug string. 调试字符串。
|
||||
*/
|
||||
FString ToDebugString() const;
|
||||
};
|
||||
@@ -0,0 +1,101 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "GCS_BulletStructLibrary.h"
|
||||
#include "Subsystems/WorldSubsystem.h"
|
||||
#include "GCS_BulletSubsystem.generated.h"
|
||||
|
||||
class UGCS_AttackRequest_Bullet;
|
||||
class AGCS_BulletInstance;
|
||||
|
||||
|
||||
/**
|
||||
* Subsystem for managing bullet spawning and lifecycle.
|
||||
* 管理子弹生成和生命周期的子系统。
|
||||
*/
|
||||
UCLASS()
|
||||
class GENERICCOMBATSYSTEM_API UGCS_BulletSubsystem : public UWorldSubsystem
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
static UGCS_BulletSubsystem* Get(const UWorld* World);
|
||||
|
||||
/**
|
||||
* Spawns bullets based on the provided parameters.
|
||||
* 根据提供的参数生成子弹。
|
||||
* @param SpawnParameters The spawn parameters. 生成参数。
|
||||
* @return The spawned bullet instances. 生成的子弹实例。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category="GCS|Bullet")
|
||||
virtual TArray<AGCS_BulletInstance*> SpawnBullets(const FGCS_BulletSpawnParameters& SpawnParameters);
|
||||
|
||||
/**
|
||||
* Gets the IDs of the provided bullet instances.
|
||||
* 获取提供的子弹实例的ID。
|
||||
* @param Instances The bullet instances. 子弹实例。
|
||||
* @return The IDs of the bullets. 子弹的ID。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, Category="GCS|Bullet")
|
||||
virtual TArray<FGuid> GetIdsFromBullets(TArray<AGCS_BulletInstance*> Instances);
|
||||
|
||||
/**
|
||||
* Gets or creates bullet instances based on parameters and definition.
|
||||
* 根据参数和定义获取或创建子弹实例。
|
||||
* @param SpawnParameters The spawn parameters. 生成参数。
|
||||
* @param Definition The bullet definition. 子弹定义。
|
||||
* @return The bullet instances. 子弹实例。
|
||||
*/
|
||||
TArray<AGCS_BulletInstance*> GetOrCreateBulletInstances(const FGCS_BulletSpawnParameters& SpawnParameters, const FGCS_BulletDefinition& Definition);
|
||||
|
||||
/**
|
||||
* Retrieves a bullet instance from the pool.
|
||||
* 从池中获取子弹实例。
|
||||
* @param BulletClass The class of the bullet. 子弹的类。
|
||||
* @return The bullet instance. 子弹实例。
|
||||
*/
|
||||
AGCS_BulletInstance* TakeBulletFromPool(TSubclassOf<AGCS_BulletInstance> BulletClass);
|
||||
|
||||
/**
|
||||
* Destroys a bullet by its ID.
|
||||
* 根据ID销毁子弹。
|
||||
* @param BulletId The bullet ID. 子弹ID。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category="GCS|Bullet")
|
||||
virtual void DestroyBullet(FGuid BulletId);
|
||||
|
||||
/**
|
||||
* Creates a single bullet instance.
|
||||
* 创建单个子弹实例。
|
||||
* @param SpawnParameters The spawn parameters. 生成参数。
|
||||
* @param Definition The bullet definition. 子弹定义。
|
||||
* @return The created bullet instance. 创建的子弹实例。
|
||||
*/
|
||||
AGCS_BulletInstance* CreateBulletInstance(const FGCS_BulletSpawnParameters& SpawnParameters, const FGCS_BulletDefinition& Definition);
|
||||
|
||||
/**
|
||||
* Loads a bullet definition from a handle.
|
||||
* 从句柄加载子弹定义。
|
||||
* @param Handle The bullet definition handle. 子弹定义句柄。
|
||||
* @param OutDefinition The loaded definition (output). 加载的定义(输出)。
|
||||
* @return True if the definition was loaded successfully. 如果定义加载成功返回true。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure=false, Category="GCS|Bullet", meta=(ExpandBoolAsExecs="ReturnValue"))
|
||||
virtual bool LoadBulletDefinition(const FDataTableRowHandle& Handle, FGCS_BulletDefinition& OutDefinition);
|
||||
|
||||
/**
|
||||
* Active bullet instances mapped by their IDs.
|
||||
* 按ID映射的激活子弹实例。
|
||||
*/
|
||||
UPROPERTY()
|
||||
TMap<FGuid, TObjectPtr<AGCS_BulletInstance>> BulletInstances;
|
||||
|
||||
/**
|
||||
* Pool of bullet instances for reuse.
|
||||
* 用于重用的子弹实例池。
|
||||
*/
|
||||
UPROPERTY()
|
||||
TArray<TObjectPtr<AGCS_BulletInstance>> BulletPools;
|
||||
};
|
||||
@@ -0,0 +1,51 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "GCS_BulletStructLibrary.h"
|
||||
#include "Components/ActorComponent.h"
|
||||
#include "GCS_BulletSystemComponent.generated.h"
|
||||
|
||||
|
||||
/**
|
||||
* @note WIP
|
||||
*/
|
||||
UCLASS(Abstract, ClassGroup=(GCS), meta=(BlueprintSpawnableComponent))
|
||||
class GENERICCOMBATSYSTEM_API UGCS_BulletSystemComponent : public UActorComponent
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
// Sets default values for this component's properties
|
||||
UGCS_BulletSystemComponent();
|
||||
|
||||
protected:
|
||||
// Called when the game starts
|
||||
virtual void BeginPlay() override;
|
||||
|
||||
public:
|
||||
/**
|
||||
* Gets the bullet system component from an actor.
|
||||
* 从Actor获取子弹系统组件。
|
||||
* @param Actor The actor to query. 要查询的Actor。
|
||||
* @return The bullet system component. 子弹系统组件。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "GCS|BulletSystem", Meta = (DefaultToSelf="Actor"))
|
||||
static UGCS_BulletSystemComponent* GetBulletSystemComponent(const AActor* Actor);
|
||||
|
||||
/**
|
||||
* Finds the bullet system component on an actor.
|
||||
* 在Actor上查找子弹系统组件。
|
||||
* @param Actor The actor to query. 要查询的Actor。
|
||||
* @param Component The found component (output). 找到的组件(输出)。
|
||||
* @return True if found. 如果找到返回true。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "GCS|BulletSystem", Meta = (DefaultToSelf="Actor", ExpandBoolAsExecs="ReturnValue"))
|
||||
static bool FindBulletSystemComponent(const AActor* Actor, UGCS_BulletSystemComponent*& Component);
|
||||
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "GCS|BulletSystem")
|
||||
void SpawnBullet(const FGCS_BulletSpawnParameters& SpawnParameters);
|
||||
|
||||
// virtual TArray<AGCS_BulletInstance*> SpawnBulletInternal(const FGCS_BulletSpawnParameters& SpawnParameters);
|
||||
};
|
||||
@@ -0,0 +1,55 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "GCS_BulletInstance.h"
|
||||
#include "GCS_SphereBulletInstance.generated.h"
|
||||
|
||||
class USphereComponent;
|
||||
|
||||
/**
|
||||
* Bullet instance with a spherical collision shape.
|
||||
* 具有球形碰撞形状的子弹实例。
|
||||
*/
|
||||
UCLASS(Abstract, Blueprintable)
|
||||
class GENERICCOMBATSYSTEM_API AGCS_SphereBulletInstance : public AGCS_BulletInstance
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
/**
|
||||
* Default constructor.
|
||||
* 默认构造函数。
|
||||
*/
|
||||
AGCS_SphereBulletInstance();
|
||||
|
||||
/**
|
||||
* Gets the bullet's shape component.
|
||||
* 获取子弹的形状组件。
|
||||
* @return The sphere component. 球形组件。
|
||||
*/
|
||||
virtual UShapeComponent* GetBulletShape_Implementation() const override;
|
||||
|
||||
protected:
|
||||
/**
|
||||
* Called when the game starts or when spawned.
|
||||
* 游戏开始或生成时调用。
|
||||
*/
|
||||
virtual void BeginPlay() override;
|
||||
|
||||
/**
|
||||
* The sphere component for collision detection.
|
||||
* 用于碰撞检测的球形组件。
|
||||
*/
|
||||
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="GCS")
|
||||
TObjectPtr<USphereComponent> Sphere;
|
||||
|
||||
public:
|
||||
/**
|
||||
* Called every frame.
|
||||
* 每帧调用。
|
||||
* @param DeltaTime Time since last frame. 上一帧以来的时间。
|
||||
*/
|
||||
virtual void Tick(float DeltaTime) override;
|
||||
};
|
||||
@@ -0,0 +1,205 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "GameplayTagContainer.h"
|
||||
#include "GCS_TraceDelegates.h"
|
||||
#include "GCS_ActorOwnedObject.h"
|
||||
#include "GCS_TraceSystemComponent.h"
|
||||
#include "Abilities/GameplayAbilityTypes.h"
|
||||
#include "DEPRECATED_GCS_CollisionTraceInstance.generated.h"
|
||||
|
||||
class UPrimitiveComponent;
|
||||
class UGCS_AttackRequest_Base;
|
||||
class UTargetingPreset;
|
||||
class UGCS_AttackRequest_Melee;
|
||||
class UGCS_TraceSystemComponent;
|
||||
|
||||
/**
|
||||
* Object for managing collision trace instances.
|
||||
* 管理碰撞检测实例的对象。
|
||||
*/
|
||||
UCLASS(Blueprintable, AutoExpandCategories = ("GCS"), Deprecated, meta=(DeprecationMessage="CollisionTraceInstance is nolonger required since GCS 1.5!"))
|
||||
class GENERICCOMBATSYSTEM_API UDEPRECATED_GCS_CollisionTraceInstance : public UGCS_ActorOwnedObject
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
friend UGCS_TraceSystemComponent;
|
||||
|
||||
public:
|
||||
/**
|
||||
* Gameplay tag for the trace instance.
|
||||
* 碰撞检测实例的游戏标签。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "GCS|Trace Settings", meta=(ExposeOnSpawn))
|
||||
FGameplayTag TraceGameplayTag;
|
||||
|
||||
/**
|
||||
* The primitive component used for tracing.
|
||||
* 用于追踪的原始组件。
|
||||
*/
|
||||
UPROPERTY(BlueprintReadWrite, Category = "GCS|Trace Settings", meta=(ExposeOnSpawn))
|
||||
TObjectPtr<UPrimitiveComponent> TracePrimitiveComponent;
|
||||
|
||||
/**
|
||||
* Socket names on the primitive component for tracing.
|
||||
* 原始组件上用于追踪的插槽名称。
|
||||
*/
|
||||
UPROPERTY(BlueprintReadWrite, Category = "GCS|Trace Settings", meta=(ExposeOnSpawn))
|
||||
TArray<FName> TracePrimitiveComponentSocketNames;
|
||||
|
||||
/**
|
||||
* Targeting preset for fetching target actors.
|
||||
* 用于获取目标Actor的目标预设。
|
||||
*/
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="GCS|Trace Settings", meta=(ExposeOnSpawn))
|
||||
TObjectPtr<UTargetingPreset> TargetingPreset;
|
||||
|
||||
/**
|
||||
* The actor that created this trace instance.
|
||||
* 创建此碰撞检测实例的Actor。
|
||||
*/
|
||||
UPROPERTY(BlueprintReadOnly, Category = "GCS|Trace Settings")
|
||||
TObjectPtr<AActor> TraceOwner = nullptr;
|
||||
|
||||
/**
|
||||
* Associated information for this trace.
|
||||
* 此碰撞检测的关联信息。
|
||||
*/
|
||||
UPROPERTY(VisibleAnywhere, Transient, Category = "GCS|Trace State")
|
||||
FGameplayEventData TraceInformation;
|
||||
|
||||
/**
|
||||
* Delegate for trace hit events.
|
||||
* 碰撞检测命中事件的委托。
|
||||
*/
|
||||
UPROPERTY(BlueprintAssignable, BlueprintCallable)
|
||||
FGCS_OnTraceHitSignature OnHit;
|
||||
|
||||
/**
|
||||
* Delegate for trace state change events.
|
||||
* 碰撞检测状态更改事件的委托。
|
||||
*/
|
||||
UPROPERTY(BlueprintAssignable, BlueprintCallable)
|
||||
FGCS_OnTraceStateChangedSignature OnTraceStateChangedEvent;
|
||||
|
||||
/**
|
||||
* The active duration of the trace instance.
|
||||
* 碰撞检测实例的激活时间。
|
||||
*/
|
||||
UPROPERTY(BlueprintReadOnly, Category="GCS|Trace State")
|
||||
float ActiveTime{0.0f};
|
||||
|
||||
/**
|
||||
* Broadcasts a hit event.
|
||||
* 广播命中事件。
|
||||
* @param HitResult The hit result. 命中结果。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category="GCS|Trace")
|
||||
void BroadcastHit(const FHitResult& HitResult);
|
||||
|
||||
/**
|
||||
* Broadcasts a state change event.
|
||||
* 广播状态更改事件。
|
||||
* @param bNewState The new state. 新状态。
|
||||
*/
|
||||
void BroadcastStateChanged(bool bNewState);
|
||||
|
||||
protected:
|
||||
/**
|
||||
* Initializes the trace instance.
|
||||
* 初始化碰撞检测实例。
|
||||
*/
|
||||
UFUNCTION(BlueprintNativeEvent, Category = "GCS|Trace", meta=(BlueprintProtected))
|
||||
void OnTraceBeginPlay();
|
||||
virtual void OnTraceBeginPlay_Implementation();
|
||||
|
||||
/**
|
||||
* Cleans up the trace instance.
|
||||
* 清理碰撞检测实例。
|
||||
* @note The instance is returned to the cache pool instead of being destroyed.
|
||||
* @注意 实例被返回到缓存池而不是销毁。
|
||||
*/
|
||||
UFUNCTION(BlueprintNativeEvent, Category = "GCS|Trace", meta=(BlueprintProtected))
|
||||
void OnTraceEndPlay();
|
||||
virtual void OnTraceEndPlay_Implementation();
|
||||
|
||||
/**
|
||||
* Handles trace ticking.
|
||||
* 处理碰撞检测的tick。
|
||||
* @param DeltaSeconds Time since last frame. 上一帧以来的时间。
|
||||
*/
|
||||
UFUNCTION(BlueprintNativeEvent, Category = "GCS|Trace", meta=(BlueprintProtected))
|
||||
void OnTraceTick(float DeltaSeconds);
|
||||
virtual void OnTraceTick_Implementation(float DeltaSeconds);
|
||||
|
||||
/**
|
||||
* Handles trace state changes.
|
||||
* 处理碰撞检测状态更改。
|
||||
* @param bNewState The new state. 新状态。
|
||||
*/
|
||||
UFUNCTION(BlueprintNativeEvent, Category = "GCS|Trace", meta=(BlueprintProtected))
|
||||
void OnTraceStateChanged(bool bNewState);
|
||||
virtual void OnTraceStateChanged_Implementation(bool bNewState);
|
||||
|
||||
/**
|
||||
* Actors hit during the active duration.
|
||||
* 激活期间命中的Actor。
|
||||
*/
|
||||
UPROPERTY(BlueprintReadWrite, EditDefaultsOnly, Category="GCS|Trace State", AdvancedDisplay)
|
||||
TArray<TObjectPtr<AActor>> HitActors;
|
||||
|
||||
public:
|
||||
/**
|
||||
* Sets the trace mesh information.
|
||||
* 设置碰撞检测网格信息。
|
||||
* @param NewPrimitiveComponent The new primitive component. 新原始组件。
|
||||
* @param PrimitiveComponentSocketNames The socket names. 插槽名称。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "GCS|Trace")
|
||||
void SetTraceMeshInfo(UPrimitiveComponent* NewPrimitiveComponent, TArray<FName> PrimitiveComponentSocketNames);
|
||||
|
||||
/**
|
||||
* Checks if an actor can be hit.
|
||||
* 检查是否可以命中Actor。
|
||||
* @param ActorToCheck The actor to check. 要检查的Actor。
|
||||
* @return True if the actor can be hit. 如果可以命中返回true。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, BlueprintNativeEvent, Category="GCS|Trace")
|
||||
bool CanHitActor(const AActor* ActorToCheck) const;
|
||||
bool CanHitActor_Implementation(const AActor* ActorToCheck) const;
|
||||
|
||||
/**
|
||||
* Gets the source actor for the trace.
|
||||
* 获取碰撞检测的源Actor。
|
||||
* @return The source actor (e.g., weapon, bullet, or trace owner). 源Actor(例如武器、子弹或TraceOwner)。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "GCS|Trace")
|
||||
AActor* GetTraceSourceActor() const;
|
||||
|
||||
/**
|
||||
* Toggles the trace state.
|
||||
* 切换碰撞检测状态。
|
||||
* @param bNewState The new state (active traces tick and attempt to hit). 新状态(激活的追踪会tick并尝试命中)。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "GCS|Trace")
|
||||
void ToggleTraceState(bool bNewState);
|
||||
|
||||
/**
|
||||
* Indicates if the trace is active.
|
||||
* 表示碰撞检测是否激活。
|
||||
*/
|
||||
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "GCS|Trace")
|
||||
bool bTraceActive{false};
|
||||
|
||||
protected:
|
||||
/**
|
||||
* Handles trace hit events.
|
||||
* 处理碰撞检测命中事件。
|
||||
* @param HitResult The hit result. 命中结果。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category = "GCS|Trace", meta=(BlueprintProtected))
|
||||
void OnTraceHit(const FHitResult& HitResult);
|
||||
virtual void OnTraceHit_Implementation(const FHitResult& HitResult);
|
||||
};
|
||||
@@ -0,0 +1,115 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "GCS_TraceStructLibrary.h"
|
||||
#include "Engine/CancellableAsyncAction.h"
|
||||
#include "GCS_AsyncAction_CollisionTrace.generated.h"
|
||||
|
||||
class UGCS_TraceSystemComponent;
|
||||
|
||||
/**
|
||||
* Async action for setting up and listening to collision trace hits.
|
||||
* 设置并监听碰撞检测命中的异步动作。
|
||||
*/
|
||||
UCLASS()
|
||||
class GENERICCOMBATSYSTEM_API UGCS_AsyncAction_CollisionTrace : public UCancellableAsyncAction
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
/**
|
||||
* Creates and activates trace instances from definitions and listens for hits.
|
||||
* 从定义创建并激活碰撞检测实例并监听命中。
|
||||
* @param TraceSystem The collision trace system component. 碰撞检测系统组件。
|
||||
* @param TraceDefinitions The traces definitions will be created and added to collision trace system. 要新建的碰撞实例的定义。
|
||||
* @param PrimitiveComponent The primitive component for tracing. 用于追踪的原始组件。
|
||||
* @param OptionalSourceObject The optional source object. 可选的源对象.
|
||||
* @return The async action instance. 异步动作实例。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, BlueprintCosmetic, Category="GUIS", meta = (WorldContext = "WorldContextObject", BlueprintInternalUseOnly = "true"))
|
||||
static UGCS_AsyncAction_CollisionTrace* SetupAndListenForCollisionTraceHit(UGCS_TraceSystemComponent* TraceSystem, const TArray<FGCS_TraceDefinition>& TraceDefinitions,
|
||||
UPrimitiveComponent* PrimitiveComponent, UObject* OptionalSourceObject = nullptr);
|
||||
|
||||
/**
|
||||
* Activates the async action.
|
||||
* 激活异步动作。
|
||||
*/
|
||||
virtual void Activate() override;
|
||||
|
||||
/**
|
||||
* Cancels the async action.
|
||||
* 取消异步动作。
|
||||
*/
|
||||
virtual void Cancel() override;
|
||||
|
||||
/**
|
||||
* Delegate for collision trace hit events.
|
||||
* 碰撞检测命中事件的委托。
|
||||
*/
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FCollisionTraceSignature, const FGCS_TraceHandle, Handle, const FHitResult&, HitResult);
|
||||
|
||||
/**
|
||||
* Called before trace instances are activated.
|
||||
* 在激活碰撞检测实例前调用。
|
||||
*/
|
||||
/**
|
||||
* Called before trace instances are activated.
|
||||
* 在激活碰撞检测实例前调用。
|
||||
*/
|
||||
UPROPERTY(BlueprintAssignable)
|
||||
FCollisionTraceSignature BeforeActive;
|
||||
|
||||
/**
|
||||
* Fired when a trace instance hits something.
|
||||
* 当碰撞检测实例命中某物时触发。
|
||||
*/
|
||||
UPROPERTY(BlueprintAssignable)
|
||||
FCollisionTraceSignature OnHit;
|
||||
|
||||
protected:
|
||||
/**
|
||||
* Handles trace instance hit events.
|
||||
* 处理碰撞检测实例命中事件。
|
||||
* @param TraceHandle The trace instance. 碰撞检测实例。
|
||||
* @param HitResult The hit result. 命中结果。
|
||||
*/
|
||||
UFUNCTION()
|
||||
void TraceHitCallback(const FGCS_TraceHandle& TraceHandle, const FHitResult& HitResult);
|
||||
|
||||
/**
|
||||
* The collision system component.
|
||||
* 碰撞系统组件。
|
||||
*/
|
||||
UPROPERTY()
|
||||
TWeakObjectPtr<UGCS_TraceSystemComponent> TraceSystem;
|
||||
|
||||
/**
|
||||
* The primitive component for tracing.
|
||||
* 用于追踪的原始组件。
|
||||
*/
|
||||
UPROPERTY()
|
||||
TWeakObjectPtr<UPrimitiveComponent> SourceComponent;
|
||||
|
||||
/**
|
||||
* The optional source object.
|
||||
* 可选的源对象。
|
||||
*/
|
||||
UPROPERTY()
|
||||
TWeakObjectPtr<UObject> SourceObject;
|
||||
|
||||
/**
|
||||
* The trace definitions to create.
|
||||
* 要创建的碰撞检测定义。
|
||||
*/
|
||||
UPROPERTY()
|
||||
TArray<FGCS_TraceDefinition> TraceDefinitions;
|
||||
|
||||
/**
|
||||
* The monitored trace instances.
|
||||
* 监听的碰撞检测实例。
|
||||
*/
|
||||
UPROPERTY()
|
||||
TArray<FGCS_TraceHandle> TraceHandles;
|
||||
};
|
||||
@@ -0,0 +1,24 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "GCS_TraceStructLibrary.h"
|
||||
#include "UObject/Object.h"
|
||||
#include "GCS_TraceDelegates.generated.h"
|
||||
|
||||
/**
|
||||
* Delegate for trace instance hit events.
|
||||
* 碰撞检测实例命中事件的委托。
|
||||
*/
|
||||
UDELEGATE()
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FGCS_OnTraceHitSignature, const FGCS_TraceHandle&, TraceHandle, const FHitResult&, HitResult);
|
||||
|
||||
UDELEGATE()
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FGCS_OnTraceStateChangedSignature, const FGCS_TraceHandle&, TraceHandle, bool, bNewState);
|
||||
|
||||
UDELEGATE()
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FGCS_OnTraceStartedSignature, const FGCS_TraceHandle&, TraceHandle);
|
||||
|
||||
UDELEGATE()
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FGCS_OnTraceStoppedSignature, const FGCS_TraceHandle&, TraceHandle);
|
||||
@@ -0,0 +1,40 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "UObject/Object.h"
|
||||
#include "GCS_TraceEnumLibrary.generated.h"
|
||||
|
||||
|
||||
UENUM()
|
||||
enum class EGCS_CollisionShapeType : uint8
|
||||
{
|
||||
Sphere,
|
||||
Box,
|
||||
Capsule
|
||||
};
|
||||
|
||||
UENUM()
|
||||
enum class EGCS_TraceSweepType : uint8
|
||||
{
|
||||
ByChannel,
|
||||
ByObject,
|
||||
ByProfile
|
||||
};
|
||||
|
||||
UENUM()
|
||||
enum class EGCS_TraceTickType : uint8
|
||||
{
|
||||
Default,
|
||||
FixedFrameRate,
|
||||
DistanceBased
|
||||
};
|
||||
|
||||
UENUM()
|
||||
enum class EGCS_TraceExecutionState : uint8
|
||||
{
|
||||
InProgress,
|
||||
Stopped,
|
||||
PendingStop
|
||||
};
|
||||
@@ -0,0 +1,29 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "GCS_TraceStructLibrary.h"
|
||||
#include "Kismet/BlueprintFunctionLibrary.h"
|
||||
#include "GCS_TraceFunctionLibrary.generated.h"
|
||||
|
||||
/**
|
||||
* Blueprint function library for trace system utilities.
|
||||
* 碰撞检测系统工具的蓝图函数库。
|
||||
*/
|
||||
UCLASS()
|
||||
class GENERICCOMBATSYSTEM_API UGCS_TraceFunctionLibrary : public UBlueprintFunctionLibrary
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
/**
|
||||
* Filter definitions with trace tag matches TagToMatch.
|
||||
* 筛选与指定标签匹配的碰撞检测定义。
|
||||
* @param Definitions The definitions to filter. 要筛选的定义。
|
||||
* @param TagToMatch The tag to check. 要检查的标签。
|
||||
* @return Matching definitions. 匹配的定义。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "GCS|TraceSystem|Utilities", Meta = (DefaultToSelf="Actor"))
|
||||
static TArray<FGCS_TraceDefinition> FilterTraceDefinitionsByTag(const TArray<FGCS_TraceDefinition>& Definitions, const FGameplayTag& TagToMatch);
|
||||
};
|
||||
@@ -0,0 +1,469 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "GameplayTagContainer.h"
|
||||
#include "CollisionShape.h"
|
||||
#include "CollisionQueryParams.h"
|
||||
#include "Collision/GCS_TraceEnumLibrary.h"
|
||||
#include "Engine/DataTable.h"
|
||||
#include "StructUtils/InstancedStruct.h"
|
||||
#include "UObject/Object.h"
|
||||
#include "GCS_TraceStructLibrary.generated.h"
|
||||
|
||||
class UTargetingPreset;
|
||||
class UGCS_TraceSystemComponent;
|
||||
|
||||
USTRUCT(BlueprintType)
|
||||
struct FGCS_TraceSweepSetting
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
/**
|
||||
* The type of sweep to perform for collision detection.
|
||||
* 执行碰撞检测的扫描类型。
|
||||
*/
|
||||
UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = "GCS")
|
||||
EGCS_TraceSweepType SweepType = EGCS_TraceSweepType::ByChannel;
|
||||
|
||||
/**
|
||||
* The collision channel to use for channel-based sweeping.
|
||||
* 用于基于通道扫描的碰撞通道。
|
||||
*/
|
||||
UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = "GCS",
|
||||
meta = (EditCondition = "SweepType == EGCS_TraceSweepType::ByChannel", EditConditionHides))
|
||||
TEnumAsByte<ECollisionChannel> TraceChannel = ECC_Visibility;
|
||||
|
||||
/**
|
||||
* The object types to use for object-based sweeping.
|
||||
* 用于基于对象扫描的对象类型。
|
||||
*/
|
||||
UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = "GCS",
|
||||
meta = (EditCondition = "SweepType == EGCS_TraceSweepType::ByObject", EditConditionHides))
|
||||
TArray<TEnumAsByte<ECollisionChannel>> ObjectTypes;
|
||||
|
||||
/**
|
||||
* The collision profile name to use for profile-based sweeping.
|
||||
* 用于基于配置扫描的碰撞配置名称。
|
||||
*/
|
||||
UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = "GCS",
|
||||
meta = (EditCondition = "SweepType == EGCS_TraceSweepType::ByProfile", EditConditionHides))
|
||||
FName ProfileName = NAME_None;
|
||||
|
||||
/**
|
||||
* Whether to trace against complex collision.
|
||||
* 是否对复杂碰撞进行追踪。
|
||||
*/
|
||||
UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = "GCS")
|
||||
bool bTraceComplex = false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Build collision shape by static parameters
|
||||
* 通过静态参数构建碰撞形状。
|
||||
* 在SourceComponent空间内的静态形状。
|
||||
*/
|
||||
USTRUCT(meta = (Hidden, DisplayName = "Collision Shape"))
|
||||
struct GENERICCOMBATSYSTEM_API FGCS_CollisionShape
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
virtual ~FGCS_CollisionShape() = default;
|
||||
|
||||
virtual bool InitializeShape(const UPrimitiveComponent* SourceComponent);
|
||||
virtual FTransform GetTransform(const UPrimitiveComponent* SourceComponent, const float& Time) const;
|
||||
virtual FCollisionShape GetDynamicCollisionShape(const UPrimitiveComponent* SourceComponent, const float& Time) const;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Build collision shape by static parameters
|
||||
* 通过静态参数构建碰撞形状。
|
||||
* 在SourceComponent空间内的静态形状。
|
||||
*/
|
||||
USTRUCT(meta = (DisplayName = "Collision Shape (Static)"))
|
||||
struct GENERICCOMBATSYSTEM_API FGCS_CollisionShape_Static : public FGCS_CollisionShape
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
/**
|
||||
* The type of collision shape to use.
|
||||
* 要使用的碰撞形状类型。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, Category = "GCS")
|
||||
EGCS_CollisionShapeType ShapeType = EGCS_CollisionShapeType::Sphere;
|
||||
|
||||
/**
|
||||
* The orientation/rotation of the collision shape.
|
||||
* 碰撞形状的方向/旋转。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, Category = "GCS",
|
||||
meta = (EditCondition = "ShapeType != EGCS_CollisionShapeType::Sphere", EditConditionHides))
|
||||
FRotator Orientation = FRotator::ZeroRotator;
|
||||
|
||||
/**
|
||||
* The position offset of the collision shape.
|
||||
* 碰撞形状的位置偏移。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, Category = "GCS")
|
||||
FVector Offset = FVector::ZeroVector;
|
||||
|
||||
/**
|
||||
* The radius of the sphere or capsule.
|
||||
* 球体或胶囊体的半径。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, Category = "GCS",
|
||||
meta = (EditCondition = "ShapeType != EGCS_CollisionShapeType::Box", EditConditionHides))
|
||||
float Radius = 10.f;
|
||||
|
||||
/**
|
||||
* The half height of the capsule.
|
||||
* 胶囊体的半高。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, Category = "GCS",
|
||||
meta = (EditCondition = "ShapeType == EGCS_CollisionShapeType::Capsule", EditConditionHides))
|
||||
float HalfHeight = 10.f;
|
||||
|
||||
/**
|
||||
* The half size of the box.
|
||||
* 盒子的半尺寸。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, Category = "GCS",
|
||||
meta = (EditCondition = "ShapeType == EGCS_CollisionShapeType::Box", EditConditionHides))
|
||||
FVector HalfSize = FVector(10.f, 10.f, 10.f);
|
||||
|
||||
virtual bool InitializeShape(const UPrimitiveComponent* SourceComponent) override;
|
||||
virtual FTransform GetTransform(const UPrimitiveComponent* SourceComponent, const float& Time) const override;
|
||||
virtual FCollisionShape GetDynamicCollisionShape(const UPrimitiveComponent* SourceComponent, const float& Time) const override;
|
||||
};
|
||||
|
||||
/**
|
||||
* Build dynamic collision shape by binding to shape component(Box/Sphere/Capsule).
|
||||
* 通过绑定Shape组件(Box/Sphere/Capsule)构建动态碰撞形状。
|
||||
* 在SourceComponent空间内的,与SourceComponent进行匹配的形状。
|
||||
*/
|
||||
USTRUCT(meta = (DisplayName = "Collision Shape (Shape Based)"))
|
||||
struct GENERICCOMBATSYSTEM_API FGCS_CollisionShape_ShapeBased : public FGCS_CollisionShape
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
/**
|
||||
* The type of collision shape to use.
|
||||
* 要使用的碰撞形状类型。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, Category = "GCS")
|
||||
EGCS_CollisionShapeType ShapeType = EGCS_CollisionShapeType::Sphere;
|
||||
|
||||
/**
|
||||
* The orientation/rotation of the collision shape.
|
||||
* 碰撞形状的方向/旋转。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, Category = "GCS",
|
||||
meta = (EditCondition = "ShapeType != EGCS_CollisionShapeType::Sphere", EditConditionHides))
|
||||
FRotator Orientation = FRotator::ZeroRotator;
|
||||
|
||||
UPROPERTY(EditAnywhere, Category = "GCS")
|
||||
FVector Offset = FVector::ZeroVector;
|
||||
|
||||
UPROPERTY()
|
||||
float Radius = 10.f;
|
||||
|
||||
UPROPERTY()
|
||||
float HalfHeight = 10.f;
|
||||
|
||||
UPROPERTY()
|
||||
FVector HalfSize = FVector(10.f, 10.f, 10.f);
|
||||
|
||||
virtual bool InitializeShape(const UPrimitiveComponent* SourceComponent) override;
|
||||
virtual FTransform GetTransform(const UPrimitiveComponent* SourceComponent, const float& Time) const override;
|
||||
virtual FCollisionShape GetDynamicCollisionShape(const UPrimitiveComponent* SourceComponent, const float& Time) const override;
|
||||
};
|
||||
|
||||
/**
|
||||
* 在SourceComponent空间内,跟随SourceComponent的某Socket/Bone运动的形状。
|
||||
*/
|
||||
USTRUCT(meta = (DisplayName = "Collision Shape (Attached)"))
|
||||
struct GENERICCOMBATSYSTEM_API FGCS_CollisionShape_Attached : public FGCS_CollisionShape_Static
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
/**
|
||||
* Name of the socket or bone the shape will be attached to.
|
||||
* 形状将附加到的插槽或骨骼的名称。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, Category="GCS")
|
||||
FName SocketOrBoneName;
|
||||
|
||||
virtual bool InitializeShape(const UPrimitiveComponent* SourceComponent) override;
|
||||
virtual FTransform GetTransform(const UPrimitiveComponent* SourceComponent, const float& Time) const override;
|
||||
};
|
||||
|
||||
/**
|
||||
* Build dynamic capsule collision shape by mesh's sockets.
|
||||
* 通过网格的指定Sockets构建动态碰撞形状。
|
||||
*/
|
||||
USTRUCT(meta = (DisplayName = "Collision Shape (Socket Based)"))
|
||||
struct GENERICCOMBATSYSTEM_API FGCS_CollisionShape_SocketBased : public FGCS_CollisionShape
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
/**
|
||||
* The name of the starting socket on the mesh.
|
||||
* 网格上起始插槽的名称。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, Category = "GCS")
|
||||
FName MeshSocketStart = TEXT("TrailStart");
|
||||
|
||||
/**
|
||||
* The name of the ending socket on the mesh.
|
||||
* 网格上结束插槽的名称。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, Category = "GCS")
|
||||
FName MeshSocketEnd = TEXT("TrailEnd");
|
||||
|
||||
/**
|
||||
* Additional length offset for the socket-based shape.
|
||||
* 基于插槽的形状的额外长度偏移。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, Category = "GCS")
|
||||
float MeshSocketLengthOffset = 0;
|
||||
|
||||
/**
|
||||
* The radius of the capsule.
|
||||
* 胶囊体的半径。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, Category = "GCS")
|
||||
float Radius = 10.f;
|
||||
|
||||
/**
|
||||
* The orientation/rotation of the collision shape.
|
||||
* 碰撞形状的方向/旋转。
|
||||
*/
|
||||
UPROPERTY()
|
||||
FRotator Orientation = FRotator::ZeroRotator;
|
||||
|
||||
/**
|
||||
* The position offset of the collision shape.
|
||||
* 碰撞形状的位置偏移。
|
||||
*/
|
||||
UPROPERTY()
|
||||
FVector Offset = FVector::ZeroVector;
|
||||
|
||||
/**
|
||||
* The half height of the capsule.
|
||||
* 胶囊体的半高。
|
||||
*/
|
||||
UPROPERTY()
|
||||
float HalfHeight = 10.f;
|
||||
|
||||
virtual FTransform GetTransform(const UPrimitiveComponent* SourceComponent, const float& Time) const override;
|
||||
virtual bool InitializeShape(const UPrimitiveComponent* SourceComponent) override;
|
||||
virtual FCollisionShape GetDynamicCollisionShape(const UPrimitiveComponent* SourceComponent, const float& Time) const override;
|
||||
};
|
||||
|
||||
/**
|
||||
* Structure for defining collision trace instances.
|
||||
* 定义碰撞检测实例的结构。
|
||||
*/
|
||||
USTRUCT(BlueprintType)
|
||||
struct GENERICCOMBATSYSTEM_API FGCS_TraceDefinition : public FTableRowBase
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
FGCS_TraceDefinition();
|
||||
|
||||
/**
|
||||
* Tag used as Trace identifier.
|
||||
* 此Trace的Tag标识。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Trace Settings", meta=(Categories="GGF.Combat.Trace"))
|
||||
FGameplayTag TraceTag;
|
||||
|
||||
/**
|
||||
* Defines the shape used for detecting hit results.
|
||||
* 定义用于获取命中结果的形状。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, Category = "Trace Settings", meta=(ExcludeBaseStruct, BaseStruct = "/Script/GenericCombatSystem.GCS_CollisionShape"))
|
||||
FInstancedStruct CollisionShape{FInstancedStruct::Make(FGCS_CollisionShape_Static())};
|
||||
|
||||
/**
|
||||
* Settings for the sweep operation.
|
||||
* 扫描操作的设置。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Trace Settings")
|
||||
FGCS_TraceSweepSetting SweepSetting;
|
||||
|
||||
/**
|
||||
* The type of tick policy to use for this trace.
|
||||
* 此Trace使用的tick策略类型。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Trace Settings")
|
||||
EGCS_TraceTickType TraceTickType{EGCS_TraceTickType::FixedFrameRate};
|
||||
|
||||
/**
|
||||
* The fixed frame rate for ticking when using FixedFrameRate policy.
|
||||
* 使用固定帧率策略时的固定帧率。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Trace Settings",
|
||||
meta = (EditCondition = "TraceTickType == EGCS_TraceTickType::FixedFrameRate", EditConditionHides))
|
||||
int32 FixedTickFrameRate = 30;
|
||||
|
||||
/**
|
||||
* The distance threshold for ticking when using DistanceBased policy.
|
||||
* 使用基于距离策略时的距离阈值。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Trace Settings",
|
||||
meta = (EditCondition = "TraceTickType == EGCS_TraceTickType::DistanceBased", EditConditionHides))
|
||||
int32 DistanceTickThreshold = 30;
|
||||
|
||||
/**
|
||||
* The angle threshold for ticking when using DistanceBased policy (in degrees).
|
||||
* 使用基于距离策略时的角度阈值(度)。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Trace Settings",
|
||||
meta = (EditCondition = "TraceTickType == EGCS_TraceTickType::DistanceBased", EditConditionHides))
|
||||
float AngleTickThreshold = 15.0f; // Degrees
|
||||
|
||||
bool IsValidDefinition() const;
|
||||
|
||||
FString ToString() const;
|
||||
};
|
||||
|
||||
/**
|
||||
* Simple struct used for trace query.
|
||||
*/
|
||||
USTRUCT(BlueprintType)
|
||||
struct GENERICCOMBATSYSTEM_API FGCS_TraceHandle
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
/**
|
||||
* The gameplay tag identifying this trace.
|
||||
* 标识此Trace的游戏标签。
|
||||
*/
|
||||
UPROPERTY(BlueprintReadOnly, Category = "GCS")
|
||||
FGameplayTag TraceTag;
|
||||
|
||||
/**
|
||||
* The unique GUID for this trace instance.
|
||||
* 此Trace实例的唯一GUID。
|
||||
*/
|
||||
UPROPERTY(BlueprintReadOnly, Category = "GCS")
|
||||
FGuid Guid;
|
||||
|
||||
/**
|
||||
* The source object that created this trace.
|
||||
* 创建此Trace的源对象。
|
||||
*/
|
||||
UPROPERTY(BlueprintReadOnly, Category = "GCS")
|
||||
TWeakObjectPtr<UObject> SourceObject;
|
||||
|
||||
bool IsValidHandle() const;
|
||||
|
||||
// Equality operator
|
||||
friend bool operator==(const FGCS_TraceHandle& A, const FGCS_TraceHandle& B)
|
||||
{
|
||||
return A.TraceTag == B.TraceTag &&
|
||||
A.Guid == B.Guid &&
|
||||
A.SourceObject == B.SourceObject;
|
||||
}
|
||||
|
||||
// Inequality operator
|
||||
friend bool operator!=(const FGCS_TraceHandle& A, const FGCS_TraceHandle& B)
|
||||
{
|
||||
return !(A == B);
|
||||
}
|
||||
|
||||
// Hash function for TMap/TSet
|
||||
friend uint32 GetTypeHash(const FGCS_TraceHandle& Handle)
|
||||
{
|
||||
uint32 Hash = GetTypeHash(Handle.TraceTag);
|
||||
Hash = HashCombine(Hash, GetTypeHash(Handle.Guid));
|
||||
Hash = HashCombine(Hash, GetTypeHash(Handle.SourceObject));
|
||||
return Hash;
|
||||
}
|
||||
|
||||
FString ToDebugString() const
|
||||
{
|
||||
FString TagName = TraceTag.ToString();
|
||||
if (TraceTag.IsValid())
|
||||
{
|
||||
TArray<FString> TagNames;
|
||||
TraceTag.ToString().ParseIntoArray(TagNames,TEXT("."));
|
||||
if (TagNames.Num() > 0)
|
||||
{
|
||||
TagName = TagNames.Last();
|
||||
}
|
||||
}
|
||||
|
||||
return FString::Printf(TEXT("%s;%s"), *TagName, SourceObject.IsValid() ? *GetNameSafe(SourceObject.Get()) : TEXT("None"));
|
||||
}
|
||||
};
|
||||
|
||||
// 补帧数据.
|
||||
struct FGCS_TraceSubTick
|
||||
{
|
||||
FTransform StartTransform;
|
||||
FTransform EndTransform;
|
||||
FTransform AverageTransform;
|
||||
};
|
||||
|
||||
USTRUCT(BlueprintType)
|
||||
struct FGCS_TraceState
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
void ChangeExecutionState(bool bNewTraceState, bool bStopImmediate = true);
|
||||
void UpdatePreviousTransform(const FTransform& Transform);
|
||||
|
||||
// Get the world space transform of this trace.
|
||||
FTransform GetCurrentTransform() const;
|
||||
|
||||
// Begin Runtime references
|
||||
UPROPERTY()
|
||||
UWorld* World{nullptr};
|
||||
UPROPERTY()
|
||||
TObjectPtr<UPrimitiveComponent> SourceComponent{nullptr};
|
||||
UPROPERTY()
|
||||
TObjectPtr<UGCS_TraceSystemComponent> OwningSystem{nullptr};
|
||||
// End Runtime references
|
||||
|
||||
// Begin Static Data
|
||||
FGCS_TraceSweepSetting SweepSetting;
|
||||
// End Static Data
|
||||
|
||||
// Begin runtime data
|
||||
FGCS_TraceHandle Handle;
|
||||
|
||||
bool IsPendingRemoval = false;
|
||||
|
||||
// The modified dynamic shape.
|
||||
FInstancedStruct Shape;
|
||||
|
||||
TArray<FTransform, TFixedAllocator<2>> TransformsOverTime;
|
||||
|
||||
EGCS_TraceExecutionState ExecutionState = EGCS_TraceExecutionState::Stopped;
|
||||
|
||||
FCollisionShape CollisionShapeOverTime;
|
||||
TArray<FGCS_TraceSubTick> SubTicks;
|
||||
|
||||
EGCS_TraceTickType TickPolicy;
|
||||
float TickInterval = 1 / 30;
|
||||
float AngleThreshold = 15.0f; // Degrees
|
||||
bool bShouldTickThisFrame = false;
|
||||
|
||||
float TimeSinceLastTick = 0;
|
||||
// how long this state active?
|
||||
|
||||
//
|
||||
float TimeSinceActive = 0;
|
||||
int32 TotalTickNumDuringExecution = 0;
|
||||
TArray<TObjectPtr<AActor>> HitActors;
|
||||
|
||||
FCollisionQueryParams CollisionParams;
|
||||
FCollisionResponseParams ResponseParams;
|
||||
FCollisionObjectQueryParams ObjectQueryParams;
|
||||
|
||||
// End runtime data
|
||||
};
|
||||
@@ -0,0 +1,60 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "GCS_TraceStructLibrary.h"
|
||||
#include "DrawDebugHelpers.h"
|
||||
#include "WorldCollision.h"
|
||||
#include "Kismet/KismetSystemLibrary.h"
|
||||
#include "Subsystems/WorldSubsystem.h"
|
||||
#include "GCS_TraceSubsystem.generated.h"
|
||||
|
||||
UCLASS()
|
||||
class GENERICCOMBATSYSTEM_API UGCS_TraceSubsystem : public UTickableWorldSubsystem
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
virtual void Initialize(FSubsystemCollectionBase& Collection) override;
|
||||
|
||||
virtual void Tick(float DeltaTime) override;
|
||||
virtual bool IsTickable() const override { return true; }
|
||||
virtual TStatId GetStatId() const override { return TStatId(); }
|
||||
|
||||
FCriticalSection CriticalSection;
|
||||
bool IsValidStateIdx(int32 StateIdx) const;
|
||||
FGCS_TraceState& GetTraceStateAt(int Index);
|
||||
|
||||
int32 AddTraceState();
|
||||
void RemoveTraceState(int Idx, FGuid Guid);
|
||||
|
||||
protected:
|
||||
TArray<FGCS_TraceState> TraceStates;
|
||||
bool RemovalLock = false;
|
||||
uint32 TickIdx = 0;
|
||||
void RemoveTraceStateAt(int Idx, FGuid Guid);
|
||||
|
||||
void PreTraceTick(const float DeltaTime);
|
||||
void PostTraceTick();
|
||||
|
||||
// 计算所有用于碰撞检测的必须数据。
|
||||
virtual void PrepareSubTicks(const float DeltaTime);
|
||||
virtual void PerformSubTicks(const float DeltaTime);
|
||||
|
||||
static void PerformAsyncTrace(const FTransform& StartTransform, const FTransform& EndTransform, const FTransform& AverageTransform, UWorld* World,
|
||||
const FGCS_TraceSweepSetting& TraceSettings,
|
||||
const FCollisionShape& CollisionShape, const FCollisionQueryParams& CollisionParams,
|
||||
const FCollisionResponseParams& CollisionResponseParams,
|
||||
const FCollisionObjectQueryParams& ObjectQueryParams, const FTraceDelegate* InDelegate = nullptr);
|
||||
|
||||
virtual void HandleTraceResults(const FTraceHandle& InTraceHandle, FTraceDatum& InTraceDatum, int32 TraceStateIdx, uint32 InTickIdx, float InShapeTime);
|
||||
|
||||
#if ENABLE_DRAW_DEBUG
|
||||
static void DrawDebug(const FVector& Start, const FVector& End, const FQuat& Rot, TArray<FHitResult> Hits, const FCollisionShape& CollisionShape, const UWorld* World,
|
||||
const EDrawDebugTrace::Type DrawDebugType, float DrawDebugTime, const FLinearColor& DrawDebugColor, const FLinearColor& DrawDebugHitColor);
|
||||
#endif
|
||||
|
||||
// 新增:用于批量移除的Pending列表,避免边遍历边修改
|
||||
TArray<TPair<int32, FGuid>> PendingRemovals;
|
||||
};
|
||||
@@ -0,0 +1,312 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Components/ActorComponent.h"
|
||||
#include "GameplayTagContainer.h"
|
||||
#include "GCS_TraceDelegates.h"
|
||||
#include "GCS_TraceStructLibrary.h"
|
||||
#include "GCS_TraceSystemComponent.generated.h"
|
||||
|
||||
class UGCS_TraceSubsystem;
|
||||
class UPrimitiveComponent;
|
||||
|
||||
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FGCS_OnTraceSystemDestroyedSignature);
|
||||
|
||||
|
||||
/**
|
||||
* Component for managing collision trace instances.
|
||||
* 管理碰撞检测实例的组件。
|
||||
*/
|
||||
UCLASS(ClassGroup=(GCS), meta=(BlueprintSpawnableComponent, PrioritizeCategories="GCS"), Blueprintable, HideCategories=(Sockets, Navigation, Tags, ComponentTick, ComponentReplication,
|
||||
Cooking, AssetUserData, Replication))
|
||||
class GENERICCOMBATSYSTEM_API UGCS_TraceSystemComponent : public UActorComponent
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
friend UGCS_TraceSubsystem;
|
||||
|
||||
public:
|
||||
/**
|
||||
* Default constructor.
|
||||
* 默认构造函数。
|
||||
*/
|
||||
UGCS_TraceSystemComponent(const FObjectInitializer& ObjectInitializer);
|
||||
|
||||
/**
|
||||
* Gets the collision trace system component from an actor.
|
||||
* 从Actor获取碰撞检测系统组件。
|
||||
* @param Actor The actor to query. 要查询的Actor。
|
||||
* @return The collision trace system component. 碰撞检测系统组件。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "GCS|TraceSystem", Meta = (DefaultToSelf="Actor"))
|
||||
static UGCS_TraceSystemComponent* GetTraceSystemComponent(const AActor* Actor);
|
||||
|
||||
/**
|
||||
* Finds the collision trace system component on an actor.
|
||||
* 在Actor上查找碰撞检测系统组件。
|
||||
* @param Actor The actor to query. 要查询的Actor。
|
||||
* @param Component The found component (output). 找到的组件(输出)。
|
||||
* @return True if found. 如果找到返回true。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "GCS|TraceSystem", Meta = (DefaultToSelf="Actor", ExpandBoolAsExecs="ReturnValue"))
|
||||
static bool FindTraceSystemComponent(const AActor* Actor, UGCS_TraceSystemComponent*& Component);
|
||||
|
||||
/**
|
||||
* Initializes the collision trace system.
|
||||
* 初始化碰撞检测系统。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category = "GCS|TraceSystem", meta=(DisplayName="Initialize Trace System"))
|
||||
void OnInitialize();
|
||||
virtual void OnInitialize_Implementation();
|
||||
|
||||
/**
|
||||
* Deinitializes the collision trace system.
|
||||
* 取消初始化碰撞检测系统。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category = "GCS|TraceSystem", meta=(DisplayName="Deinitialize Trace System"))
|
||||
void OnDeinitialize();
|
||||
virtual void OnDeinitialize_Implementation();
|
||||
|
||||
/**
|
||||
* Creates trace instances from definitions.
|
||||
* 从定义创建碰撞检测实例。
|
||||
* @param Definitions The trace definitions. 碰撞检测定义。
|
||||
* @param SourceComponent The primitive component for tracing. 用于追踪的原始组件。
|
||||
* @param SourceObject The source object. 源对象。
|
||||
* @return The created trace instances. 创建的碰撞检测实例。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "GCS|TraceSystem")
|
||||
TArray<FGCS_TraceHandle> AddTracesByDefinitions(const TArray<FGCS_TraceDefinition>& Definitions, UPrimitiveComponent* SourceComponent, UObject* SourceObject);
|
||||
|
||||
/**
|
||||
* Creates trace instances from data table row handles.
|
||||
* 从数据表行句柄创建碰撞检测实例。
|
||||
* @param DefinitionHandles The data table row handles for trace definitions. 碰撞检测定义的数据表行句柄。
|
||||
* @param SourceComponent The primitive component for tracing. 用于追踪的原始组件。
|
||||
* @param SourceObject The source object. 源对象。
|
||||
* @return The created trace instances. 创建的碰撞检测实例。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "GCS|TraceSystem")
|
||||
TArray<FGCS_TraceHandle> AddTracesByDefinitionHandles(UPARAM(meta=(RowType="/Script/GenericCombatSystem.GCS_CollisionTraceDefinition"))
|
||||
const TArray<FDataTableRowHandle>& DefinitionHandles, UPrimitiveComponent* SourceComponent, UObject* SourceObject);
|
||||
|
||||
/**
|
||||
* Creates a single trace instance from a definition.
|
||||
* 从定义创建单个碰撞检测实例。
|
||||
* @param Definition The trace definition. 碰撞检测定义。
|
||||
* @param SourceComponent The primitive component for tracing. 用于追踪的原始组件。
|
||||
* @param SourceObject The source object. 源对象。
|
||||
* @return The created trace instance. 创建的碰撞检测实例。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "GCS|TraceSystem")
|
||||
FGCS_TraceHandle AddTraceByDefinition(const FGCS_TraceDefinition& Definition, UPrimitiveComponent* SourceComponent, UObject* SourceObject);
|
||||
|
||||
/**
|
||||
* Starts traces by tags and source object.
|
||||
* 通过标签和源对象启动碰撞检测。
|
||||
* @param TraceTags The gameplay tags to match. 要匹配的游戏标签。
|
||||
* @param SourceObject The source object. 源对象。
|
||||
* @return The started trace instances. 启动的碰撞检测实例。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "GCS|TraceSystem")
|
||||
TArray<FGCS_TraceHandle> StartTracesByTagsAndSource(const FGameplayTagContainer& TraceTags, const UObject* SourceObject);
|
||||
|
||||
/**
|
||||
* Starts multiple traces by their handles.
|
||||
* 通过句柄启动多个碰撞检测。
|
||||
* @param TraceHandles The trace handles to start. 要启动的碰撞检测句柄。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "GCS|TraceSystem")
|
||||
void StartTracesByHandles(const TArray<FGCS_TraceHandle>& TraceHandles);
|
||||
|
||||
/**
|
||||
* Starts a single trace by its handle.
|
||||
* 通过句柄启动单个碰撞检测。
|
||||
* @param TraceHandle The trace handle to start. 要启动的碰撞检测句柄。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "GCS|TraceSystem")
|
||||
void StartTraceByHandle(const FGCS_TraceHandle& TraceHandle);
|
||||
|
||||
/**
|
||||
* Stops multiple traces by their handles.
|
||||
* 通过句柄停止多个碰撞检测。
|
||||
* @param TraceHandles The trace handles to stop. 要停止的碰撞检测句柄。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "GCS|TraceSystem")
|
||||
void StopTracesByHandles(const TArray<FGCS_TraceHandle>& TraceHandles);
|
||||
|
||||
/**
|
||||
* Stops a single trace by its handle.
|
||||
* 通过句柄停止单个碰撞检测。
|
||||
* @param TraceHandle The trace handle to stop. 要停止的碰撞检测句柄。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "GCS|TraceSystem")
|
||||
void StopTraceByHandle(const FGCS_TraceHandle& TraceHandle);
|
||||
|
||||
/**
|
||||
* Removes a trace by its handle.
|
||||
* 通过句柄移除碰撞检测。
|
||||
* @param TraceHandle The trace handle to remove. 要移除的碰撞检测句柄。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "GCS|TraceSystem")
|
||||
void RemoveTraceByHandle(const FGCS_TraceHandle& TraceHandle);
|
||||
|
||||
/**
|
||||
* Clears all active and cached trace instances.
|
||||
* 清除所有激活和缓存的碰撞检测实例。
|
||||
* @note OnTraceEndPlay event is not fired. OnTraceEndPlay事件不会触发。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "GCS|TraceSystem")
|
||||
void RemoveAllTraces();
|
||||
|
||||
/**
|
||||
* Gets all trace handles with the specified tag.
|
||||
* 获取具有指定标签的所有碰撞检测句柄。
|
||||
* @param TraceToFind The gameplay tag to search for. 要搜索的游戏标签。
|
||||
* @return Array of trace handles with the specified tag. 具有指定标签的碰撞检测句柄数组。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "GCS|TraceSystem")
|
||||
TArray<FGCS_TraceHandle> GetTraceHandlesByTag(FGameplayTag TraceToFind) const;
|
||||
|
||||
/**
|
||||
* Gets all trace handles created by the specified source object.
|
||||
* 获取由指定源对象创建的所有碰撞检测句柄。
|
||||
* @param SourceObject The source object to search for. 要搜索的源对象。
|
||||
* @return Array of trace handles from the specified source. 来自指定源的碰撞检测句柄数组。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "GCS|TraceSystem")
|
||||
TArray<FGCS_TraceHandle> GetTraceHandlesBySource(const UObject* SourceObject) const;
|
||||
|
||||
/**
|
||||
* Gets all trace handles with the specified tags and source object.
|
||||
* 获取具有指定标签和源对象的所有碰撞检测句柄。
|
||||
* @param TraceTags The gameplay tags to match. 要匹配的游戏标签。
|
||||
* @param SourceObject The source object to match. 要匹配的源对象。
|
||||
* @return Array of trace handles matching the criteria. 匹配条件的碰撞检测句柄数组。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "GCS|TraceSystem")
|
||||
TArray<FGCS_TraceHandle> GetTraceHandlesByTagsAndSource(const FGameplayTagContainer& TraceTags, const UObject* SourceObject) const;
|
||||
|
||||
/**
|
||||
* Gets the source actor for the specified trace handle.
|
||||
* 获取指定碰撞检测句柄的源Actor。
|
||||
* @param TraceHandle The trace handle to query. 要查询的碰撞检测句柄。
|
||||
* @return The source actor, or nullptr if not found. 源Actor,如果未找到则返回nullptr。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "GCS|TraceSystem")
|
||||
AActor* GetTraceSourceActor(const FGCS_TraceHandle& TraceHandle) const;
|
||||
|
||||
/**
|
||||
* Gets the source component for the specified trace handle.
|
||||
* 获取指定碰撞检测句柄的源组件。
|
||||
* @param TraceHandle The trace handle to query. 要查询的碰撞检测句柄。
|
||||
* @return The source component, or nullptr if not found. 源组件,如果未找到则返回nullptr。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "GCS|TraceSystem")
|
||||
UPrimitiveComponent* GetTraceSourceComponent(const FGCS_TraceHandle& TraceHandle) const;
|
||||
|
||||
/**
|
||||
* Checks if the specified trace is currently active.
|
||||
* 检查指定的碰撞检测当前是否激活。
|
||||
* @param TraceHandle The trace handle to check. 要检查的碰撞检测句柄。
|
||||
* @return True if the trace is active. 如果碰撞检测激活则返回true。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "GCS|TraceSystem")
|
||||
bool IsTraceActive(const FGCS_TraceHandle& TraceHandle) const;
|
||||
|
||||
virtual TArray<FGCS_TraceHandle> StartTraces(const FGameplayTagContainer& TraceTags, const UObject* SourceObject);
|
||||
|
||||
virtual TArray<FGCS_TraceHandle> AddTraces(const TArray<FGCS_TraceDefinition>& Definitions, UPrimitiveComponent* SourceComponent, UObject* SourceObject);
|
||||
virtual TArray<FGCS_TraceHandle> AddTraces(const TArray<FDataTableRowHandle>& DefinitionHandles, UPrimitiveComponent* SourceComponent, UObject* SourceObject);
|
||||
virtual FGCS_TraceHandle AddTrace(const FGCS_TraceDefinition& TraceDefinition, UPrimitiveComponent* SourceComponent, UObject* SourceObject);
|
||||
virtual FGCS_TraceHandle AddTrace(const FDataTableRowHandle& TraceDefinitionHandle, UPrimitiveComponent* SourceComponent, UObject* SourceObject);
|
||||
|
||||
virtual void StartTraces(const TArray<FGCS_TraceHandle>& TraceHandles);
|
||||
|
||||
virtual void StartTrace(const FGCS_TraceHandle& TraceHandle);
|
||||
|
||||
virtual void StopTraces(const TArray<FGCS_TraceHandle>& TraceHandles);
|
||||
|
||||
virtual void StopTrace(const FGCS_TraceHandle& TraceHandle);
|
||||
|
||||
virtual void RemoveTraces(const TArray<FGCS_TraceHandle>& TraceHandles);
|
||||
virtual void RemoveTrace(const FGCS_TraceHandle& TraceHandle);
|
||||
|
||||
/**
|
||||
* Event fired when a trace hits something.
|
||||
* 当碰撞检测命中某物时触发的事件。
|
||||
*/
|
||||
UPROPERTY(BlueprintAssignable, Category = "GCS|TraceSystem")
|
||||
FGCS_OnTraceHitSignature OnTraceHitEvent;
|
||||
|
||||
/**
|
||||
* Event fired when a trace state changes.
|
||||
* 当碰撞检测状态改变时触发的事件。
|
||||
*/
|
||||
UPROPERTY(BlueprintAssignable, Category = "GCS|TraceSystem")
|
||||
FGCS_OnTraceStateChangedSignature OnTraceStateChangedEvent;
|
||||
|
||||
/**
|
||||
* Event fired when a trace starts.
|
||||
* 当碰撞检测开始时触发的事件。
|
||||
*/
|
||||
UPROPERTY(BlueprintAssignable, Category = "GCS|TraceSystem")
|
||||
FGCS_OnTraceStartedSignature OnTraceStartedEvent;
|
||||
|
||||
/**
|
||||
* Event fired when a trace stops.
|
||||
* 当碰撞检测停止时触发的事件。
|
||||
*/
|
||||
UPROPERTY(BlueprintAssignable, Category = "GCS|TraceSystem")
|
||||
FGCS_OnTraceStoppedSignature OnTraceStoppedEvent;
|
||||
|
||||
/**
|
||||
* Event fired when the trace system is destroyed.
|
||||
* 当碰撞检测系统被销毁时触发的事件。
|
||||
*/
|
||||
FGCS_OnTraceSystemDestroyedSignature OnDestroyedEvent;
|
||||
|
||||
protected:
|
||||
virtual void OnTraceHitDetected(const FGCS_TraceHandle& TraceHandle, const TArray<FHitResult>& HitResults, const float DeltaTime, const uint32 TickIdx);
|
||||
|
||||
virtual void OnTraceStateChanged(const FGCS_TraceHandle& TraceHandle, bool bNewState);
|
||||
|
||||
/**
|
||||
* Called when the game starts.
|
||||
* 游戏开始时调用。
|
||||
*/
|
||||
virtual void BeginPlay() override;
|
||||
|
||||
/**
|
||||
* Called when the game ends.
|
||||
* 游戏结束时调用。
|
||||
* @param EndPlayReason The reason for ending. 结束原因。
|
||||
*/
|
||||
virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override;
|
||||
|
||||
virtual void OnComponentDestroyed(bool bDestroyingHierarchy) override;
|
||||
|
||||
/**
|
||||
* Whether to auto-initialize on BeginPlay and deinitialize on EndPlay.
|
||||
* 是否在BeginPlay时自动初始化,在EndPlay时自动取消初始化。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, Category = "GCS|TraceSystem")
|
||||
bool bAutoInitialize{true};
|
||||
|
||||
/**
|
||||
* Default trace definitions created on BeginPlay.
|
||||
* 在BeginPlay时创建的默认碰撞检测定义。
|
||||
* @note SourceComponent will be the actor's main mesh. 源组件会是Actor的主要mesh。
|
||||
*/
|
||||
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "GCS|TraceSystem", meta=(TitleProperty="TraceTag"))
|
||||
TArray<FGCS_TraceDefinition> TraceDefinitions;
|
||||
|
||||
FCriticalSection TraceDoneScopeLock;
|
||||
|
||||
TMap<FGCS_TraceHandle, int32> HandleToStateIdx;
|
||||
TMultiMap<FGameplayTag, FGCS_TraceHandle> TagToHandles;
|
||||
bool bInitialized = false;
|
||||
};
|
||||
@@ -0,0 +1,49 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "GCS_CombatStructLibrary.h"
|
||||
#include "Engine/DataAsset.h"
|
||||
#include "GCS_AbilityActionSetSettings.generated.h"
|
||||
|
||||
class UGCS_LayeredMontageSelectionSet;
|
||||
|
||||
/**
|
||||
* Data asset for defining ability action sets.
|
||||
* 定义能力动作集的数据资产。
|
||||
*/
|
||||
UCLASS(BlueprintType, Const, meta=(DisplayName="GCS Ability Action Set"))
|
||||
class GENERICCOMBATSYSTEM_API UGCS_AbilityActionSetSettings : public UDataAsset
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
/**
|
||||
* Selects the best ability actions based on tags.
|
||||
* 根据标签选择最佳能力动作。
|
||||
* @param SourceTags Tags for the source. 来源标签。
|
||||
* @param TargetTags Tags for the target. 目标标签。
|
||||
* @param AbilityTags Tags for the ability. 能力标签。
|
||||
* @param Actions The matched ability actions (output). 匹配的能力动作(输出)。
|
||||
* @return True if selection is successful. 如果选择成功返回true。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure=false, Category = "GCS", meta=(AutoCreateRefTerm="TargetTags"))
|
||||
bool SelectBestAbilityActions(const FGameplayTagContainer& SourceTags, const FGameplayTagContainer& TargetTags, const FGameplayTagContainer& AbilityTags, TArray<FGCS_AbilityAction>& Actions) const;
|
||||
|
||||
/**
|
||||
* Array of ability action sets.
|
||||
* 能力动作集数组。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, Category="GCS", meta=(TitleProperty="AbilityTag"))
|
||||
TArray<FGCS_AbilityActionSet> ActionSets;
|
||||
|
||||
#if WITH_EDITORONLY_DATA
|
||||
/**
|
||||
* Called before saving in the editor.
|
||||
* 编辑器中保存前调用。
|
||||
* @param SaveContext The save context. 保存上下文。
|
||||
*/
|
||||
virtual void PreSave(FObjectPreSaveContext SaveContext) override;
|
||||
#endif
|
||||
};
|
||||
@@ -0,0 +1,142 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "GameplayEffect.h"
|
||||
#include "Engine/DataTable.h"
|
||||
#include "GameplayTagContainer.h"
|
||||
#include "GGA_AbilitySystemStructLibrary.h"
|
||||
#include "Runtime/Launch/Resources/Version.h"
|
||||
#if ENGINE_MINOR_VERSION < 5
|
||||
#include "InstancedStruct.h"
|
||||
#else
|
||||
#include "StructUtils/InstancedStruct.h"
|
||||
#endif
|
||||
#include "GCS_AttackDefinition.generated.h"
|
||||
|
||||
|
||||
/**
|
||||
* Base struct allow you to extend the attack definition's fields using C++.
|
||||
* 基础结构体,允许你通过C++拓展攻击定义的字段。
|
||||
*/
|
||||
USTRUCT(BlueprintType, meta=(Hidden))
|
||||
struct GENERICCOMBATSYSTEM_API FGCS_AttackDefinitionExtension
|
||||
{
|
||||
GENERATED_BODY()
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Structure defining an attack's properties.
|
||||
* 定义攻击属性的结构。
|
||||
*/
|
||||
USTRUCT(BlueprintType, meta=(DisplayName="GCS Attack Definition"))
|
||||
struct GENERICCOMBATSYSTEM_API FGCS_AttackDefinition : public FTableRowBase
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
/**
|
||||
* Tags describing the attack (e.g., Melee/Ranged, Slash/Strike).
|
||||
* 描述攻击的标签(例如近战/远程、劈砍/打击)。
|
||||
* @note Added as dynamic AssetTags to the gameplay effect spec.
|
||||
* @注意 作为动态AssetTags添加到游戏效果规格。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Common")
|
||||
FGameplayTagContainer AttackTags;
|
||||
|
||||
/**
|
||||
* SetByCaller tag-to-float mappings for gameplay effect specs.
|
||||
* 用于游戏效果规格的SetByCaller标签到浮点映射。
|
||||
* @note Usage is flexible (e.g., damage correction factors).
|
||||
* @注意 使用灵活(例如伤害修正系数)。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Common", meta=(ForceInlineRow))
|
||||
TMap<FGameplayTag, float> SetByCallerMagnitudes;
|
||||
|
||||
/**
|
||||
* Gameplay effect to apply to the hit target.
|
||||
* 应用于命中目标的游戏效果。
|
||||
* @note Modified during attack request processing.
|
||||
* @注意 在攻击请求处理期间修改。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Gameplay Effects")
|
||||
TSoftClassPtr<UGameplayEffect> TargetEffectClass;
|
||||
|
||||
/**
|
||||
* Level of the target gameplay effect.
|
||||
* 目标游戏效果的等级。
|
||||
* @note If < 1, uses the ability's level.
|
||||
* @注意 如果<1,使用能力的等级。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Gameplay Effects")
|
||||
int32 TargetEffectClassLevel{1};
|
||||
|
||||
/**
|
||||
* Effect container to apply to the target.
|
||||
* 应用于目标的效果容器。
|
||||
* @note Used for instant targeting; ability level determines effect level.
|
||||
* @注意 用于即时目标;能力等级决定效果等级。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Gameplay Effects", meta=(ForceInlineRow))
|
||||
FGGA_GameplayEffectContainer TargetEffectContainer;
|
||||
|
||||
/**
|
||||
* Gameplay cues to trigger on the target upon hit.
|
||||
* 命中目标时触发的游戏反馈。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Gameplay Cues", meta=(Categories="GameplayCue"))
|
||||
TArray<FGameplayTag> TargetGameplayCues;
|
||||
|
||||
/**
|
||||
* Knockback distance applied to the target.
|
||||
* 应用于目标的击退距离。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "HitReaction", meta=(Units="cm"))
|
||||
float KnockbackDistance{100};
|
||||
|
||||
/**
|
||||
* Multiplier for knockback effect.
|
||||
* 击退效果的倍增器。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "HitReaction", meta=(ClampMin=1))
|
||||
float KnockbackMultiplier{1};
|
||||
|
||||
/**
|
||||
* Duration of animation stall on hit (disabled if <= 0).
|
||||
* 命中时动画停滞的持续时间(<=0时禁用)。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Feedback", meta=(ClampMin=0, Units="s"))
|
||||
float HitStallingDuration{0};
|
||||
|
||||
/**
|
||||
* Play rate factor for hit animation.
|
||||
* 命中动画的播放速率因子。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Feedback", meta=(ClampMin=0.1, ClampMax=0.9))
|
||||
float HitPlayRateFactor{0.1};
|
||||
|
||||
/**
|
||||
* Native Instanced struct for extending the attack definition.
|
||||
* 实例化结构体用于扩充攻击定义的字段。
|
||||
* @attention For C++ users only. 仅针对C++用户。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Extension")
|
||||
TInstancedStruct<FGCS_AttackDefinitionExtension> NativeExtension;
|
||||
|
||||
/**
|
||||
* Blueprint Instanced struct for extending the attack definition.
|
||||
* 实例化结构体用于扩充攻击定义的字段。
|
||||
* @attention For blueprint users only. 仅针对蓝图用户。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Extension")
|
||||
FInstancedStruct Extension;
|
||||
|
||||
/**
|
||||
* User-defined settings for the attack.
|
||||
* 攻击的用户定义设置。
|
||||
*/
|
||||
UE_DEPRECATED(1.5, "Using extension field to add custom fields!")
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Deprecated", meta=(ForceInlineRow, BaseStruct = "/Script/GenericCombatSystem.GCS_UserSetting"))
|
||||
TMap<FGameplayTag, FInstancedStruct> UserSettings;
|
||||
};
|
||||
@@ -0,0 +1,224 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "GameplayTagContainer.h"
|
||||
#include "Bullet/GCS_BulletStructLibrary.h"
|
||||
#include "GCS_AttackDefinition.h"
|
||||
#include "UObject/Object.h"
|
||||
#include "GCS_AttackRequest.generated.h"
|
||||
|
||||
/**
|
||||
* Base class for all attack request types.
|
||||
* 所有攻击请求类型的基类。
|
||||
*/
|
||||
UCLASS(Abstract, Blueprintable, BlueprintType, Const, DefaultToInstanced, EditInlineNew, meta=(DisplayName="GCS Attack Request"))
|
||||
class UGCS_AttackRequest_Base : public UObject
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
/**
|
||||
* Gets the attack definition handle.
|
||||
* 获取攻击定义句柄。
|
||||
* @return The attack definition handle. 攻击定义句柄。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, BlueprintNativeEvent, Category="GCS|Attack")
|
||||
FDataTableRowHandle GetAttackDefinitionHandle() const;
|
||||
|
||||
/**
|
||||
* Gets the attack definition.
|
||||
* 获取攻击定义。
|
||||
* @return The attack definition. 攻击定义。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, Category="GCS|Attack")
|
||||
FGCS_AttackDefinition GetAttackDefinition() const;
|
||||
};
|
||||
|
||||
/**
|
||||
* Attack request for melee attacks.
|
||||
* 近战攻击请求。
|
||||
*/
|
||||
UCLASS(meta=(DisplayName="GCS Attack Request (Melee)"))
|
||||
class GENERICCOMBATSYSTEM_API UGCS_AttackRequest_Melee : public UGCS_AttackRequest_Base
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
/**
|
||||
* Gets the attack definition handle.
|
||||
* 获取攻击定义句柄。
|
||||
* @return The attack definition handle. 攻击定义句柄。
|
||||
*/
|
||||
virtual FDataTableRowHandle GetAttackDefinitionHandle_Implementation() const override;
|
||||
|
||||
/**
|
||||
* Tags for traces activated during the notify state.
|
||||
* 在通知状态期间激活的追踪标签。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="Attack")
|
||||
FGameplayTagContainer TracesToControl;
|
||||
|
||||
protected:
|
||||
/**
|
||||
* Handle to the attack definition.
|
||||
* 攻击定义的句柄。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="Attack", meta=(RowType="/Script/GenericCombatSystem.GCS_AttackDefinition"))
|
||||
FDataTableRowHandle AttackDefinitionHandle;
|
||||
};
|
||||
|
||||
/**
|
||||
* Enum for ability targeting source types.
|
||||
* 能力目标来源类型的枚举。
|
||||
*/
|
||||
UENUM(BlueprintType)
|
||||
enum class EGCS_AbilityTargetingSourceType : uint8
|
||||
{
|
||||
/**
|
||||
* From the player's camera towards camera focus.
|
||||
* 从玩家相机朝向相机焦点。
|
||||
*/
|
||||
CameraTowardsFocus,
|
||||
|
||||
/**
|
||||
* From the pawn's location/socket, in the pawn's orientation.
|
||||
* 从Pawn的位置/插槽,沿Pawn的朝向。
|
||||
*/
|
||||
PawnForward,
|
||||
|
||||
/**
|
||||
* From the pawn's location/socket, oriented towards camera focus.
|
||||
* 从Pawn的位置/插槽,朝向相机焦点。
|
||||
*/
|
||||
PawnTowardsFocus,
|
||||
|
||||
/**
|
||||
* From the weapon's location/socket, in the pawn's orientation.
|
||||
* 从武器的位置/插槽,沿Pawn的朝向。
|
||||
*/
|
||||
WeaponForward,
|
||||
|
||||
/**
|
||||
* From the weapon's location/socket, towards camera focus.
|
||||
* 从武器的位置/插槽,朝向相机焦点。
|
||||
*/
|
||||
WeaponTowardsFocus,
|
||||
|
||||
/**
|
||||
* Custom targeting, requires overriding GetTargetingTransform.
|
||||
* 自定义目标,需重写GetTargetingTransform。
|
||||
*/
|
||||
Custom
|
||||
};
|
||||
|
||||
/**
|
||||
* Attack request for firing bullets.
|
||||
* 发射子弹的攻击请求。
|
||||
*/
|
||||
UCLASS(Blueprintable, BlueprintType, Const, EditInlineNew, meta=(DisplayName="GCS Attack Request (Bullet)"))
|
||||
class GENERICCOMBATSYSTEM_API UGCS_AttackRequest_Bullet : public UGCS_AttackRequest_Base
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
/**
|
||||
* Gets the bullet definition.
|
||||
* 获取子弹定义。
|
||||
* @return The bullet definition. 子弹定义。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "GCS|Attack")
|
||||
FGCS_BulletDefinition GetBulletDefinition() const;
|
||||
|
||||
/**
|
||||
* Gets the attack definition handle.
|
||||
* 获取攻击定义句柄。
|
||||
* @return The attack definition handle. 攻击定义句柄。
|
||||
*/
|
||||
virtual FDataTableRowHandle GetAttackDefinitionHandle_Implementation() const override;
|
||||
|
||||
/**
|
||||
* Gets the targeting transform for the attack.
|
||||
* 获取攻击的目标变换。
|
||||
* @param SourcePawn The source pawn. 来源Pawn。
|
||||
* @param Source The targeting source type. 目标来源类型。
|
||||
* @return The targeting transform. 目标变换。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, BlueprintNativeEvent, Category = "GCS|Attack")
|
||||
FTransform GetTargetingTransform(APawn* SourcePawn, EGCS_AbilityTargetingSourceType Source) const;
|
||||
|
||||
/**
|
||||
* Gets the weapon targeting source location.
|
||||
* 获取武器目标来源位置。
|
||||
* @param SourcePawn The source pawn. 来源Pawn。
|
||||
* @return The weapon source location. 武器来源位置。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, BlueprintNativeEvent, Category = "GCS|Attack")
|
||||
FVector GetWeaponTargetingSourceLocation(APawn* SourcePawn) const;
|
||||
|
||||
/**
|
||||
* Gets the pawn targeting source location.
|
||||
* 获取Pawn目标来源位置。
|
||||
* @param SourcePawn The source pawn. 来源Pawn。
|
||||
* @return The pawn source location. Pawn来源位置。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, BlueprintNativeEvent, Category = "GCS|Attack")
|
||||
FVector GetPawnTargetingSourceLocation(APawn* SourcePawn) const;
|
||||
|
||||
/**
|
||||
* Handle to the bullet definition.
|
||||
* 子弹定义的句柄。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Parameters, meta=(RowType="/Script/GenericCombatSystem.GCS_BulletDefinition"))
|
||||
FDataTableRowHandle BulletDefinitionHandle;
|
||||
|
||||
/**
|
||||
* Type of targeting source for the attack.
|
||||
* 攻击的目标来源类型。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (ExposeOnSpawn = true), Category = Parameters)
|
||||
EGCS_AbilityTargetingSourceType TargetingSourceType{EGCS_AbilityTargetingSourceType::PawnForward};
|
||||
|
||||
/**
|
||||
* Tag name for looking up the source component.
|
||||
* 用于查找来源组件的标签名称。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (ExposeOnSpawn = true), Category = Parameters, meta=(EditCondition="TargetingSourceType != EGCS_AbilityTargetingSourceType::CameraTowardsFocus"))
|
||||
FName SourceComponentLookupTagName{NAME_None};
|
||||
|
||||
/**
|
||||
* Source socket name, falls back to source location if not found.
|
||||
* 来源插槽名称,如果未找到则回退到来源位置。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (ExposeOnSpawn = true), Category = Parameters, meta=(EditCondition="TargetingSourceType != EGCS_AbilityTargetingSourceType::CameraTowardsFocus"))
|
||||
FName SourceSocketName{NAME_None};
|
||||
|
||||
/**
|
||||
* Weapon socket name, falls back to source location if not found.
|
||||
* 武器插槽名称,如果未找到则回退到来源位置。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (ExposeOnSpawn = true), Category = Parameters, meta=(EditCondition="TargetingSourceType != EGCS_AbilityTargetingSourceType::CameraTowardsFocus"))
|
||||
FName SourceWeaponSocketName{NAME_None};
|
||||
|
||||
/**
|
||||
* Additional offset to the source location.
|
||||
* 来源位置的附加偏移。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (ExposeOnSpawn = true), Category = Parameters)
|
||||
FVector LocationOffset{FVector::Zero()};
|
||||
|
||||
/**
|
||||
* Whether targeting is required for the attack.
|
||||
* 攻击是否需要目标。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Parameters)
|
||||
bool bRequireTargeting{false};
|
||||
|
||||
/**
|
||||
* Targeting preset for the attack.
|
||||
* 攻击的目标预设。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Parameters)
|
||||
TObjectPtr<UTargetingPreset> TargetingPreset;
|
||||
};
|
||||
@@ -0,0 +1,205 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "GameplayEffectTypes.h"
|
||||
#include "GCS_CombatStructLibrary.h"
|
||||
#include "Net/Serialization/FastArraySerializer.h"
|
||||
#include "UObject/Object.h"
|
||||
#include "GCS_AttackResult.generated.h"
|
||||
|
||||
class UGCS_AttackRequest_Melee;
|
||||
class UGCS_CombatFlow;
|
||||
class UGCS_CombatSystemComponent;
|
||||
|
||||
/**
|
||||
* Structure representing the result of a processed attack.
|
||||
* 表示已处理攻击结果的结构。
|
||||
*/
|
||||
USTRUCT(BlueprintType)
|
||||
struct GENERICCOMBATSYSTEM_API FGCS_AttackResult : public FFastArraySerializerItem
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
|
||||
void PostReplicatedAdd(const struct FGCS_AttackResultContainer& InArray);
|
||||
|
||||
|
||||
/**
|
||||
* Deprecated.
|
||||
* 已经弃用。
|
||||
*/
|
||||
UE_DEPRECATED(1.5, "Use TaggedValues within FGCS_ContextPayload_Combat")
|
||||
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="GCS", NotReplicated, meta=(DeprecatedProperty, DeprecationMessage="Use TaggedValues within FGCS_ContextPayload_Combat!"))
|
||||
TArray<FGCS_TaggedValue> TaggedValues;
|
||||
|
||||
/**
|
||||
* Optional object related to the attack.
|
||||
* 与攻击相关的可选对象。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GCS")
|
||||
TObjectPtr<const UObject> OptionalObject;
|
||||
|
||||
/**
|
||||
* Context handle for the gameplay effect.
|
||||
* 游戏效果的上下文句柄。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GCS")
|
||||
FGameplayEffectContextHandle EffectContextHandle;
|
||||
|
||||
/**
|
||||
* Aggregated source tags for the attack.
|
||||
* 攻击的聚合来源标签。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GCS")
|
||||
FGameplayTagContainer AggregatedSourceTags;
|
||||
|
||||
/**
|
||||
* Aggregated target tags for the attack.
|
||||
* 攻击的聚合目标标签。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GCS")
|
||||
FGameplayTagContainer AggregatedTargetTags;
|
||||
|
||||
/**
|
||||
* Whether the attack result has been consumed.
|
||||
* 攻击结果是否已被消耗。
|
||||
*/
|
||||
UPROPERTY(NotReplicated)
|
||||
bool bConsumed{false};
|
||||
|
||||
/**
|
||||
* Indicates this attack result was found in existing processed array with same prediction key.
|
||||
* 表示此攻击结果在已处理攻击结果列表中有相同的预测Key。
|
||||
*/
|
||||
UPROPERTY(NotReplicated)
|
||||
bool bWasPredicated{false};
|
||||
|
||||
/**
|
||||
* Indicates this attack result was replicated via fast array serializer.
|
||||
* 表示此攻击结果是经由fast array serializer同步而来。
|
||||
*/
|
||||
UPROPERTY(NotReplicated)
|
||||
bool bWasReplicated{false};
|
||||
};
|
||||
|
||||
/**
|
||||
* Container for storing combat results with network serialization.
|
||||
* 用于存储战斗结果的容器,支持网络序列化。
|
||||
*/
|
||||
USTRUCT(BlueprintType)
|
||||
struct GENERICCOMBATSYSTEM_API FGCS_AttackResultContainer : public FFastArraySerializer
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
/**
|
||||
* Default constructor.
|
||||
* 默认构造函数。
|
||||
*/
|
||||
FGCS_AttackResultContainer();
|
||||
|
||||
/**
|
||||
* Constructor with combat flow and max size.
|
||||
* 带有战斗流程和最大尺寸的构造函数。
|
||||
* @param InCombatFlow The combat flow instance. 战斗流程实例。
|
||||
* @param InMaxSize The maximum size of the container. 容器最大尺寸。
|
||||
*/
|
||||
FGCS_AttackResultContainer(UGCS_CombatFlow* InCombatFlow, int32 InMaxSize);
|
||||
|
||||
/**
|
||||
* Constructor with combat system component and max size.
|
||||
* 带有战斗系统组件和最大尺寸的构造函数。
|
||||
* @param InCombatSystemComponent The combat system component. 战斗系统组件。
|
||||
* @param InMaxSize The maximum size of the container. 容器最大尺寸。
|
||||
*/
|
||||
FGCS_AttackResultContainer(UGCS_CombatSystemComponent* InCombatSystemComponent, int32 InMaxSize);
|
||||
|
||||
/**
|
||||
* Sets the owning combat system component.
|
||||
* 设置所属战斗系统组件。
|
||||
* @param InCombatSystemComponent The combat system component. 战斗系统组件。
|
||||
*/
|
||||
void SetOwningCombatSystem(UGCS_CombatSystemComponent* InCombatSystemComponent) { CombatSystemComponent = InCombatSystemComponent; }
|
||||
|
||||
/**
|
||||
* Sets the combat flow.
|
||||
* 设置战斗流程。
|
||||
* @param InCombatFlow The combat flow instance. 战斗流程实例。
|
||||
*/
|
||||
void SetCombatFlow(UGCS_CombatFlow* InCombatFlow) { CombatFlow = InCombatFlow; }
|
||||
|
||||
/**
|
||||
* Adds a new attack result entry to the container.
|
||||
* 向容器添加新的攻击结果条目。
|
||||
* @param NewEntry The attack result to add. 要添加的攻击结果。
|
||||
*/
|
||||
void AddEntry(FGCS_AttackResult& NewEntry);
|
||||
|
||||
/**
|
||||
* Handles post-replication addition of entries.
|
||||
* 处理条目添加后的复制。
|
||||
* @param AddedIndices The indices of added entries. 添加的条目索引。
|
||||
* @param FinalSize The final size of the container. 容器最终尺寸。
|
||||
*/
|
||||
void PostReplicatedAdd(const TArrayView<int32> AddedIndices, int32 FinalSize);
|
||||
|
||||
/**
|
||||
* Handles post-replication changes to entries.
|
||||
* 处理条目更改后的复制。
|
||||
* @param ChangedIndices The indices of changed entries. 更改的条目索引。
|
||||
* @param FinalSize The final size of the container. 容器最终尺寸。
|
||||
*/
|
||||
void PostReplicatedChange(const TArrayView<int32>& ChangedIndices, int32 FinalSize);
|
||||
|
||||
/**
|
||||
* Serializes the container for network replication.
|
||||
* 为网络复制序列化容器。
|
||||
* @param DeltaParms The network serialization parameters. 网络序列化参数。
|
||||
* @return True if serialization is successful. 如果序列化成功返回true。
|
||||
*/
|
||||
bool NetDeltaSerialize(FNetDeltaSerializeInfo& DeltaParms)
|
||||
{
|
||||
return FastArrayDeltaSerialize<FGCS_AttackResult, FGCS_AttackResultContainer>(Results, DeltaParms, *this);
|
||||
}
|
||||
|
||||
bool HasPredictedResultWithPredictedKey(FPredictionKey PredictionKey) const;
|
||||
|
||||
private:
|
||||
/**
|
||||
* Reference to the combat flow instance.
|
||||
* 战斗流程实例的引用。
|
||||
*/
|
||||
UPROPERTY()
|
||||
TObjectPtr<UGCS_CombatFlow> CombatFlow;
|
||||
|
||||
/**
|
||||
* Reference to the combat system component.
|
||||
* 战斗系统组件的引用。
|
||||
*/
|
||||
UPROPERTY()
|
||||
TObjectPtr<UGCS_CombatSystemComponent> CombatSystemComponent;
|
||||
|
||||
/**
|
||||
* List of attack results.
|
||||
* 攻击结果列表。
|
||||
*/
|
||||
UPROPERTY()
|
||||
TArray<FGCS_AttackResult> Results;
|
||||
|
||||
/**
|
||||
* Maximum size of the container.
|
||||
* 容器最大尺寸。
|
||||
*/
|
||||
UPROPERTY()
|
||||
int32 MaxSize;
|
||||
};
|
||||
|
||||
template <>
|
||||
struct TStructOpsTypeTraits<FGCS_AttackResultContainer> : TStructOpsTypeTraitsBase2<FGCS_AttackResultContainer>
|
||||
{
|
||||
enum
|
||||
{
|
||||
WithNetDeltaSerializer = true,
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,287 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "GCS_AttackResult.h"
|
||||
#include "UObject/Object.h"
|
||||
#include "GCS_AttackResultProcessor.generated.h"
|
||||
|
||||
|
||||
UENUM()
|
||||
enum class EGCS_AttackResultProcessorPolicy
|
||||
{
|
||||
//execute when non-predicting cross all server and clients.
|
||||
Default,
|
||||
//execute in predicting client first,then server and other clients.same as default is not predicting.
|
||||
LocalPredicted,
|
||||
//execute only on server side.
|
||||
ServerOnly
|
||||
};
|
||||
|
||||
/**
|
||||
* Base class for processing attack results.
|
||||
* 处理攻击结果的基类。
|
||||
*/
|
||||
UCLASS(EditInlineNew, DefaultToInstanced, BlueprintType, Blueprintable, Abstract, Const)
|
||||
class GENERICCOMBATSYSTEM_API UGCS_AttackResultProcessor : public UObject
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
/**
|
||||
* Processes an incoming attack result.
|
||||
* 处理传入的攻击结果。
|
||||
* @param AttackResult The attack result to process. 要处理的攻击结果。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category="GCS")
|
||||
virtual bool ProcessIncomingAttackResult(const FGCS_AttackResult& AttackResult);
|
||||
|
||||
/**
|
||||
* Gets the world context for the processor.
|
||||
* 获取处理器的世界上下文。
|
||||
* @return The world context. 世界上下文。
|
||||
*/
|
||||
virtual UWorld* GetWorld() const override;
|
||||
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, BlueprintNativeEvent, Category="GCS")
|
||||
EGCS_AttackResultProcessorPolicy GetExecutePolicy() const;
|
||||
|
||||
#if WITH_EDITOR
|
||||
bool GetEditorEnableState() const { return bEditorDebugEnabled; };
|
||||
|
||||
#endif
|
||||
|
||||
protected:
|
||||
/**
|
||||
* Indicate how this processor will be executed cross network.
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="GCS")
|
||||
EGCS_AttackResultProcessorPolicy ExecutePolicy{EGCS_AttackResultProcessorPolicy::Default};
|
||||
|
||||
/**
|
||||
* Handles the incoming attack result.
|
||||
* 处理传入的攻击结果。
|
||||
* @param AttackResult The attack result to handle. 要处理的攻击结果。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category="GCS")
|
||||
void HandleIncomingAttackResult(const FGCS_AttackResult& AttackResult) const;
|
||||
virtual void HandleIncomingAttackResult_Implementation(const FGCS_AttackResult& AttackResult) const;
|
||||
|
||||
/**
|
||||
* Gets the owning actor.
|
||||
* 获取所属演员。
|
||||
* @return The owning actor. 所属演员。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, Category="GCS")
|
||||
AActor* GetOwningActor() const;
|
||||
|
||||
/**
|
||||
* Gets the owning ability system component.
|
||||
* 获取所属能力系统组件。
|
||||
* @return The ability system component. 能力系统组件。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, Category="GCS")
|
||||
UAbilitySystemComponent* GetOwningAbilitySystemComponent() const;
|
||||
|
||||
/**
|
||||
* Gets the editor-friendly name for the processor.
|
||||
* 获取处理器的编辑器友好名称。
|
||||
* @return The editor-friendly name. 编辑器友好名称。
|
||||
*/
|
||||
UFUNCTION(BlueprintNativeEvent, Category="GCS")
|
||||
FString GetEditorFriendlyName() const;
|
||||
virtual FString GetEditorFriendlyName_Implementation() const;
|
||||
|
||||
#if WITH_EDITORONLY_DATA
|
||||
|
||||
/**
|
||||
* Allowing toggle on/off this processor for debugging purpose.
|
||||
* 允许你开关此处理器,用于调试。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, Category="GCS")
|
||||
bool bEditorDebugEnabled{true};
|
||||
|
||||
/**
|
||||
* Editor-friendly name for the processor.
|
||||
* 处理器的编辑器友好名称。
|
||||
*/
|
||||
UPROPERTY(VisibleAnywhere, Category=AlwaysHidden)
|
||||
FString EditorFriendlyName;
|
||||
|
||||
// UPROPERTY(EditAnywhere, Category="GCS")
|
||||
// bool bPrintDebugString{false};
|
||||
|
||||
/**
|
||||
* Called before saving in the editor.
|
||||
* 编辑器中保存前调用。
|
||||
* @param SaveContext The save context. 保存上下文。
|
||||
*/
|
||||
virtual void PreSave(FObjectPreSaveContext SaveContext) override;
|
||||
#endif
|
||||
};
|
||||
|
||||
/**
|
||||
* Attack result processor with tag requirements.
|
||||
* 具有标签要求的攻击结果处理器。
|
||||
*/
|
||||
UCLASS(Abstract)
|
||||
class UGCS_AttackResultProcessor_WithTagRequirement : public UGCS_AttackResultProcessor
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
/**
|
||||
* Processes an incoming attack result with tag requirements.
|
||||
* 处理具有标签要求的传入攻击结果。
|
||||
* @param AttackResult The attack result to process. 要处理的攻击结果。
|
||||
*/
|
||||
virtual bool ProcessIncomingAttackResult(const FGCS_AttackResult& AttackResult) override;
|
||||
|
||||
protected:
|
||||
/**
|
||||
* Source tag query for filtering attack results.
|
||||
* 用于过滤攻击结果的来源标签查询。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="GCS")
|
||||
FGameplayTagQuery SourceTagQuery;
|
||||
|
||||
/**
|
||||
* Target tag query for filtering attack results.
|
||||
* 用于过滤攻击结果的目标标签查询。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="GCS")
|
||||
FGameplayTagQuery TargetTagQuery;
|
||||
|
||||
/**
|
||||
* Gets the description of the source tag query.
|
||||
* 获取来源标签查询的描述。
|
||||
* @return The source tag query description. 来源标签查询描述。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, Category="GCS")
|
||||
FString GetSourceTagQueryDesc() const;
|
||||
|
||||
/**
|
||||
* Gets the description of the target tag query.
|
||||
* 获取目标标签查询的描述。
|
||||
* @return The target tag query description. 目标标签查询描述。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, Category="GCS")
|
||||
FString GetTargetTagQueryDesc() const;
|
||||
};
|
||||
|
||||
/**
|
||||
* Processor for handling death-related attack results.
|
||||
* 处理与死亡相关的攻击结果的处理器。
|
||||
*/
|
||||
UCLASS()
|
||||
class UGCS_AttackResultProcessor_Death : public UGCS_AttackResultProcessor
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
/**
|
||||
* Handles death-related attack results.
|
||||
* 处理与死亡相关的攻击结果。
|
||||
* @param AttackResult The attack result to handle. 要处理的攻击结果。
|
||||
*/
|
||||
virtual void HandleIncomingAttackResult_Implementation(const FGCS_AttackResult& AttackResult) const override;
|
||||
};
|
||||
|
||||
/**
|
||||
* Processor for converting attack results to gameplay events.
|
||||
* 将攻击结果转换为游戏事件的处理器。
|
||||
* @note Only executes for server pawn or local controller pawn. The dynamic tags added to effect context will be merged as Instigator Tags.
|
||||
* @注意 仅对服务器Pawn或本地控制器Pawn执行。 添加到Effect Context的动态标签会被合并为Instigator Tags。
|
||||
*/
|
||||
UCLASS()
|
||||
class UGCS_AttackResultProcessor_GameplayEvent : public UGCS_AttackResultProcessor_WithTagRequirement
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
protected:
|
||||
/**
|
||||
* Handles attack results by converting to gameplay events.
|
||||
* 通过转换为游戏事件处理攻击结果。
|
||||
* @param AttackResult The attack result to handle. 要处理的攻击结果。
|
||||
*/
|
||||
virtual void HandleIncomingAttackResult_Implementation(const FGCS_AttackResult& AttackResult) const override;
|
||||
|
||||
/**
|
||||
* Gets the editor-friendly name for the processor.
|
||||
* 获取处理器的编辑器友好名称。
|
||||
* @return The editor-friendly name. 编辑器友好名称。
|
||||
*/
|
||||
virtual FString GetEditorFriendlyName_Implementation() const override;
|
||||
|
||||
/**
|
||||
* Whether to send the event to the attacker.
|
||||
* 是否将事件发送给攻击者。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="GCS")
|
||||
bool bSendToAttacker{false};
|
||||
|
||||
/**
|
||||
* Gameplay tags to trigger as events.
|
||||
* 作为事件触发的游戏标签。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="GCS")
|
||||
TArray<FGameplayTag> EventTriggers;
|
||||
};
|
||||
|
||||
/**
|
||||
* Processor for triggering gameplay cues from attack results.
|
||||
* 从攻击结果触发游戏反馈的处理器。
|
||||
* @note Cues do not replicate as attack results are replicated.
|
||||
* @注意 反馈不复制,因为攻击结果已复制。
|
||||
*/
|
||||
UCLASS()
|
||||
class UGCS_AttackResultProcessor_GameplayCue : public UGCS_AttackResultProcessor_WithTagRequirement
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
protected:
|
||||
/**
|
||||
* Handles attack results by triggering gameplay cues.
|
||||
* 通过触发游戏反馈处理攻击结果。
|
||||
* @param AttackResult The attack result to handle. 要处理的攻击结果。
|
||||
*/
|
||||
virtual void HandleIncomingAttackResult_Implementation(const FGCS_AttackResult& AttackResult) const override;
|
||||
|
||||
/**
|
||||
* Gets the editor-friendly name for the processor.
|
||||
* 获取处理器的编辑器友好名称。
|
||||
* @return The editor-friendly name. 编辑器友好名称。
|
||||
*/
|
||||
virtual FString GetEditorFriendlyName_Implementation() const override;
|
||||
|
||||
/**
|
||||
* Modifies gameplay cue parameters before execution.
|
||||
* 在执行前修改游戏反馈参数。
|
||||
* @param ParametersToModify The parameters to modify. 要修改的参数。
|
||||
*/
|
||||
UFUNCTION(BlueprintImplementableEvent, Category="GCS")
|
||||
void ModifyGameplayCueParametersBeforeExecute(UPARAM(ref)
|
||||
FGameplayCueParameters& ParametersToModify) const;
|
||||
|
||||
/**
|
||||
* Gameplay cues to trigger.
|
||||
* 要触发的游戏反馈。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="GCS", meta=(Categories="GameplayCue"))
|
||||
TArray<FGameplayTag> GameplayCues;
|
||||
|
||||
/**
|
||||
* Tag for finding raw magnitude in TaggedValues.
|
||||
* 在TaggedValues中查找原始幅度的标签。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="GCS")
|
||||
FGameplayTag RawMagnitudeTag;
|
||||
|
||||
/**
|
||||
* Tag for finding normalized magnitude in TaggedValues.
|
||||
* 在TaggedValues中查找归一化幅度的标签。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="GCS")
|
||||
FGameplayTag NormalizedMagnitudeTag;
|
||||
};
|
||||
@@ -0,0 +1,114 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "GameplayTagContainer.h"
|
||||
#include "GCS_ActorOwnedObject.h"
|
||||
#include "GCS_AttackResult.h"
|
||||
#include "GGA_GameplayAttributeStructLibrary.h"
|
||||
#include "GCS_CombatFlow.generated.h"
|
||||
|
||||
class UGCS_AttackResultProcessor;
|
||||
class UGCS_CombatSystemComponent;
|
||||
|
||||
/**
|
||||
* Combat flow for processing incoming attacks.
|
||||
* 处理传入攻击的战斗流程。
|
||||
* @note Typically one instance per character type (e.g., human, quadruped, mechanical).
|
||||
* @注意 通常每种角色类型一个实例(例如人类、四足动物、机械)。
|
||||
*/
|
||||
UCLASS(Abstract, BlueprintType, Blueprintable, DefaultToInstanced, EditInlineNew, CollapseCategories, meta=(DisplayName="GCS Combat Flow"))
|
||||
class GENERICCOMBATSYSTEM_API UGCS_CombatFlow : public UGCS_ActorOwnedObject
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
/**
|
||||
* Default constructor.
|
||||
* 默认构造函数。
|
||||
*/
|
||||
UGCS_CombatFlow();
|
||||
|
||||
/**
|
||||
* Retrieves lifetime replicated properties.
|
||||
* 获取生命周期复制属性。
|
||||
* @param OutLifetimeProps The lifetime properties. 生命周期属性。
|
||||
*/
|
||||
virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override;
|
||||
|
||||
/**
|
||||
* Checks if networking is supported.
|
||||
* 检查是否支持网络。
|
||||
* @return True if supported. 如果支持返回true。
|
||||
*/
|
||||
virtual bool IsSupportedForNetworking() const override { return true; }
|
||||
|
||||
/**
|
||||
* Gets the actor owning this combat flow.
|
||||
* 获取拥有此战斗流程的演员。
|
||||
* @return The owning actor. 所属演员。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "GCS|Combat Flow")
|
||||
AActor* GetFlowOwner() const { return Owner; }
|
||||
|
||||
/**
|
||||
* Initializes the combat flow with an owner.
|
||||
* 使用拥有者初始化战斗流程。
|
||||
* @param NewOwner The owning actor. 所属演员。
|
||||
*/
|
||||
void Initialize(AActor* NewOwner);
|
||||
|
||||
/**
|
||||
* Adds dynamic tags to a gameplay effect spec.
|
||||
* 为游戏效果规格添加动态标签。
|
||||
* @note Requires GGA_AbilitySystemGlobals as default AbilitySystemGlobals.
|
||||
* @注意 需要将GGA_AbilitySystemGlobals设置为默认AbilitySystemGlobals。
|
||||
* @param Spec The gameplay effect spec. 游戏效果规格。
|
||||
* @param AbilitySystemComponent The ability system component. 能力系统组件。
|
||||
* @param OutDynamicTagsAppendToSpec The tags to append (output). 要附加的标签(输出)。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category="GCS|Combat Flow")
|
||||
void HandlePreGameplayEffectSpecApply(const FGameplayEffectSpec& Spec, UAbilitySystemComponent* AbilitySystemComponent, FGameplayTagContainer& OutDynamicTagsAppendToSpec);
|
||||
|
||||
/**
|
||||
* Handles gameplay effect execution.
|
||||
* 处理游戏效果执行。
|
||||
* @param Payload The effect modification data. 效果修改数据。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category="GCS|Combat Flow")
|
||||
void HandleGameplayEffectExecute(const FGGA_GameplayEffectModCallbackData& Payload);
|
||||
virtual void HandleGameplayEffectExecute_Implementation(const FGGA_GameplayEffectModCallbackData& Payload);
|
||||
|
||||
/**
|
||||
* Handles attack results across the network.
|
||||
* 在网络上处理攻击结果。
|
||||
* @note Default implementation calls result processors.
|
||||
* @注意 默认实现调用结果处理器。
|
||||
* @param Payload The attack result. 攻击结果。
|
||||
*/
|
||||
UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "GCS|Combat Flow")
|
||||
void HandleAttackResult(const FGCS_AttackResult& Payload);
|
||||
|
||||
protected:
|
||||
/**
|
||||
* The actor owning this combat flow.
|
||||
* 拥有此战斗流程的演员。
|
||||
*/
|
||||
UPROPERTY()
|
||||
TObjectPtr<AActor> Owner;
|
||||
|
||||
/**
|
||||
* Reference to the owning combat system component.
|
||||
* 所属战斗系统组件的引用。
|
||||
*/
|
||||
UPROPERTY(BlueprintReadOnly, Category = "GCS|Combat Flow", meta=(BlueprintProtected))
|
||||
TObjectPtr<UGCS_CombatSystemComponent> CombatComponent;
|
||||
|
||||
/**
|
||||
* List of attack result processors for handling attack results.
|
||||
* 处理攻击结果的攻击结果处理器列表。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Instanced, Category = "GCS|Combat Flow Settings", meta=(TitleProperty="EditorFriendlyName"))
|
||||
TArray<TObjectPtr<UGCS_AttackResultProcessor>> AttackResultProcessors;
|
||||
};
|
||||
@@ -0,0 +1,107 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "GameplayTagContainer.h"
|
||||
#include "Engine/DataTable.h"
|
||||
#include "StructUtils/InstancedStruct.h"
|
||||
#include "GCS_ComboDefinition.generated.h"
|
||||
|
||||
/**
|
||||
* Base struct allow you to extend the combo definition's fields using C++.
|
||||
* 基础结构体,允许你通过C++拓展连击定义的字段。
|
||||
*/
|
||||
USTRUCT(BlueprintType, meta=(Hidden))
|
||||
struct GENERICCOMBATSYSTEM_API FGCS_ComboDefinitionExtension
|
||||
{
|
||||
GENERATED_BODY()
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
USTRUCT(BlueprintType)
|
||||
struct FGCS_ComboDefinition : public FTableRowBase
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Combo")
|
||||
TSoftClassPtr<class UGameplayAbility> AbilityClass;
|
||||
|
||||
/**
|
||||
* Will reset the combo step if this row was selected.(Only works if MinComboStep > 0)
|
||||
* 此行选中则会重置连招步骤。(仅在MinComboStep > 0时生效。)
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Combo")
|
||||
bool bResetComboStep{false};
|
||||
|
||||
/**
|
||||
* When Current Combo Step > this value, this won't be selected. 0 means no restriction.
|
||||
* How many combo steps required to select this combo.
|
||||
* 至少执行过几次combo,才能选择此行作为下一个combo。0代表入口。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Requirements", meta=(ClampMin=0))
|
||||
int32 MinComboStep = 0;
|
||||
|
||||
/**
|
||||
* The combo event data's event tag must equals to this tag if set, otherwise this combo won't be selected!.
|
||||
* 若有值,连击事件数据的event tag必须与此值相等,否则该连击不会被选择。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Requirements")
|
||||
FGameplayTag EventTag;
|
||||
|
||||
/**
|
||||
* The combo event data's instigator tags mush matches this Query if set, otherwise this combo won't be selected!
|
||||
* 若有值,连击事件数据的instigator tags必须匹配此查询,否则该连击不会被选择。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Requirements")
|
||||
FGameplayTagQuery EventInstigatorTagQuery;
|
||||
|
||||
/**
|
||||
* The tags owned by the ability system component of the pawn must much this query if set, otherwise this combo won't be selected!
|
||||
* 若有值,Pawn的技能组件所拥有的Tags必须满足此查询,否则该连击不会被选择。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Requirements")
|
||||
FGameplayTagQuery TagQuery;
|
||||
|
||||
/**
|
||||
* Should try if this ability can be activated before select it?
|
||||
* 是否在选择Ability之前测试是否可激活?
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Activation")
|
||||
bool bRunActivationTest{false};
|
||||
|
||||
/**
|
||||
* Should abort the whole combo or just skip this selection when activation test failed?
|
||||
* 若激活测试失败,是放弃整个Combo还是跳过此选择?
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Activation", meta=(EditCondition="bRunActivationTest"))
|
||||
bool bAbortIfActivationTestFailed{false};
|
||||
|
||||
/**
|
||||
* Native Instanced struct for extending the combo definition.
|
||||
* 实例化结构体用于扩充连击定义的字段。
|
||||
* @attention For C++ users only. 仅针对C++用户。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Extension")
|
||||
TInstancedStruct<FGCS_ComboDefinitionExtension> NativeExtension;
|
||||
|
||||
/**
|
||||
* Blueprint Instanced struct for extending the combo definition.
|
||||
* 实例化结构体用于扩充连击定义的字段。
|
||||
* @attention For blueprint users only. 仅针对蓝图用户。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Extension")
|
||||
FInstancedStruct Extension;
|
||||
|
||||
#if WITH_EDITORONLY_DATA
|
||||
/**
|
||||
* Description for developers in the editor.
|
||||
* 编辑器中用于开发者的描述。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, Category = "Editor")
|
||||
FString DevDescription;
|
||||
#endif
|
||||
};
|
||||
@@ -0,0 +1,25 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "UObject/Object.h"
|
||||
#include "GCS_ActorOwnedObject.generated.h"
|
||||
|
||||
/**
|
||||
* Base class for objects owned by an actor.
|
||||
* 演员拥有的对象的基类。
|
||||
*/
|
||||
UCLASS(NotBlueprintable, Abstract)
|
||||
class GENERICCOMBATSYSTEM_API UGCS_ActorOwnedObject : public UObject
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
/**
|
||||
* Gets the world context for the object.
|
||||
* 获取对象的世界上下文。
|
||||
* @return The world context. 世界上下文。
|
||||
*/
|
||||
virtual UWorld* GetWorld() const override;
|
||||
};
|
||||
@@ -0,0 +1,199 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "GameplayTagContainer.h"
|
||||
#include "GCS_CombatStructLibrary.h"
|
||||
#include "UObject/Interface.h"
|
||||
#include "GCS_CombatEntityInterface.generated.h"
|
||||
|
||||
class USceneComponent;
|
||||
|
||||
/**
|
||||
* Interface for actors or components involved in combat.
|
||||
* 参与战斗的演员或组件的接口。
|
||||
* @note Use helper function "GetCombatInterface" for access.
|
||||
* @注意 使用辅助函数"GetCombatInterface"访问。
|
||||
*/
|
||||
UINTERFACE(MinimalAPI, BlueprintType, Blueprintable)
|
||||
class UGCS_CombatEntityInterface : public UInterface
|
||||
{
|
||||
GENERATED_BODY()
|
||||
};
|
||||
|
||||
/**
|
||||
* Combat interface for handling combat-related functionality.
|
||||
* 处理战斗相关功能的接口。
|
||||
* @note Implementing objects should group related functionality.
|
||||
* @注意 实现对象应分组相关功能。
|
||||
*/
|
||||
class GENERICCOMBATSYSTEM_API IGCS_CombatEntityInterface
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
/**
|
||||
* Gets the current combat target actor.
|
||||
* 获取当前战斗目标演员。
|
||||
* @return The combat target actor. 战斗目标演员。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category="GCS|Combat")
|
||||
AActor* GetCombatTargetActor() const;
|
||||
|
||||
UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category="GCS|Combat")
|
||||
const UDataTable* GetComboDefinitionTable() const;
|
||||
|
||||
/**
|
||||
* Gets the current combat target object as a scene component.
|
||||
* 获取当前战斗目标对象的场景组件。
|
||||
* @return The combat target scene component. 战斗目标场景组件。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category="GCS|Combat")
|
||||
USceneComponent* GetCombatTargetObject() const;
|
||||
|
||||
/**
|
||||
* Queries ability actions based on tags.
|
||||
* 根据标签查询能力动作。
|
||||
* @param AbilityTags Tags for the ability. 能力标签。
|
||||
* @param SourceTags Source tags for filtering. 来源标签。
|
||||
* @param TargetTags Target tags for filtering. 目标标签。
|
||||
* @param AbilityActions The matching ability actions (output). 匹配的能力动作(输出)。
|
||||
* @return True if valid results are found. 如果找到有效结果返回true。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category="GCS|Combat",
|
||||
meta=(ExpandBoolAsExecs="ReturnValue", DeprecatedFunction, DeprecationMessage="QueryAbilityActionsByContext as it is more reliable!"))
|
||||
bool QueryAbilityActions(FGameplayTagContainer AbilityTags, FGameplayTagContainer SourceTags, FGameplayTagContainer TargetTags, TArray<FGCS_AbilityAction>& AbilityActions);
|
||||
|
||||
/**
|
||||
* Queries ability actions based on tags and context object.
|
||||
* 根据上下文和标签查询能力动作。
|
||||
* @param Context An optional Context object, Usually the source object if called from ability.
|
||||
* @param AbilityTags Tags for the ability. 能力标签。
|
||||
* @param SourceTags Source tags for filtering. 来源标签。
|
||||
* @param TargetTags Target tags for filtering. 目标标签。
|
||||
* @param AbilityActions The matching ability actions (output). 匹配的能力动作(输出)。
|
||||
* @return True if valid results are found. 如果找到有效结果返回true。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category="GCS|Combat", meta=(ExpandBoolAsExecs="ReturnValue"))
|
||||
bool QueryAbilityActionsByContext(UObject* Context, FGameplayTagContainer AbilityTags, FGameplayTagContainer SourceTags, FGameplayTagContainer TargetTags,
|
||||
TArray<FGCS_AbilityAction>& AbilityActions);
|
||||
|
||||
/**
|
||||
* Queries a weapon based on a tag query.
|
||||
* 根据标签查询武器。
|
||||
* @param Query The tag query for filtering. 标签查询。
|
||||
* @return The object implementing GCS_WeaponInterface. 实现武器接口的对象。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category="GCS|Combat", meta=(DisplayName="Query Weapon"))
|
||||
UObject* QueryWeapon(const FGameplayTagQuery& Query) const;
|
||||
|
||||
/**
|
||||
* Sets the character's rotation mode (e.g., strafe).
|
||||
* 设置角色的旋转模式(例如靶向移动)。
|
||||
* @param NewRotationMode The new rotation mode. 新旋转模式。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category="GCS|Movement")
|
||||
void SetRotationMode(FGameplayTag NewRotationMode);
|
||||
|
||||
/**
|
||||
* Gets the current rotation mode.
|
||||
* 获取当前旋转模式。
|
||||
* @return The current rotation mode. 当前旋转模式。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category="GCS|Movement")
|
||||
FGameplayTag GetRotationMode() const;
|
||||
|
||||
/**
|
||||
* Sets the movement set (e.g., ADS, Guard).
|
||||
* 设置运动集(例如瞄准、防御)。
|
||||
* @param NewMovementSet The new movement set. 新运动集。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category="GCS|Movement")
|
||||
void SetMovementSet(FGameplayTag NewMovementSet);
|
||||
|
||||
/**
|
||||
* Gets the current movement set.
|
||||
* 获取当前运动集。
|
||||
* @return The current movement set. 当前运动集。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category="GCS|Movement")
|
||||
FGameplayTag GetMovementSet() const;
|
||||
|
||||
/**
|
||||
* Sets the movement state (e.g., walk, jog, sprint).
|
||||
* 设置运动状态(例如走、跑、疾跑)。
|
||||
* @param NewMovementState The new movement state. 新运动状态。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category="GCS|Movement")
|
||||
void SetMovementState(FGameplayTag NewMovementState);
|
||||
|
||||
/**
|
||||
* Gets the current movement state.
|
||||
* 获取当前运动状态。
|
||||
* @return The current movement state. 当前运动状态。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category="GCS|Movement")
|
||||
FGameplayTag GetMovementState() const;
|
||||
|
||||
/**
|
||||
* Initiates the death process (e.g., disable collision, drop weapons).
|
||||
* 启动死亡流程(例如禁用碰撞、丢弃武器)。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category="GCS|Lifecycle")
|
||||
void StartDeath();
|
||||
|
||||
/**
|
||||
* Finalizes the death process (e.g., ragdoll, destroy actor).
|
||||
* 完成死亡流程(例如布娃娃、销毁演员)。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category="GCS|Lifecycle")
|
||||
void FinishDeath();
|
||||
|
||||
/**
|
||||
* Checks if the character is dead.
|
||||
* 检查角色是否死亡。
|
||||
* @return True if the character is dead. 如果角色死亡返回true。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category="GCS|Lifecycle")
|
||||
bool IsDead() const;
|
||||
|
||||
/**
|
||||
* Gets the movement input direction.
|
||||
* 获取移动输入方向。
|
||||
* @return The movement input direction. 移动输入方向。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category="GCS|Input")
|
||||
FVector GetMovementIntent() const;
|
||||
|
||||
/**
|
||||
* Gets the current used weapon. (You may have multiple weapon active at the same time, use this interface to get the current one.)
|
||||
* 获取当前使用的武器。( 你可能有多个武器同时激活,使用此接口获取当前使用的那一个。)
|
||||
* @param Context Optional context for querying. 可选查询上下文。
|
||||
* @return The object implementing GCS_WeaponInterface. 实现武器接口的对象。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category="GCS|Weapon")
|
||||
UObject* GetCurrentWeapon(UObject* Context = nullptr) const;
|
||||
|
||||
/**
|
||||
* Set the current used weapon.(You may have multiple weapon active at the same time, use this interface to set the current one.)
|
||||
* 设置当前使用的武器。(你可能有多个武器同时激活,使用此接口设置当前使用的那一个。)
|
||||
* @note This is not for weapon switching, only used to mark which weapon will be used for next action(Press X to use primary weapon/Y to use secondary weapon.). 这并非用于武器切换,仅用于标识当前使用的哪个武器(比如X使用主武器,Y使用副武器,在使用之前设置到底是哪一个。)
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category="GCS|Weapon")
|
||||
void SetCurrentWeapon(UObject* Weapon);
|
||||
|
||||
/**
|
||||
* Gets the relative transform for a mesh attached to a socket.
|
||||
* 获取附加到插槽的网格的相对变换。
|
||||
* @param InSkeletalMeshComponent The skeletal mesh component. 骨骼网格组件。
|
||||
* @param StaticMesh The static mesh. 静态网格。
|
||||
* @param SkeletalMesh The skeletal mesh. 骨骼网格。
|
||||
* @param SocketName The socket name. 插槽名称。
|
||||
* @param OutTransform The relative transform (output). 相对变换(输出)。
|
||||
* @return True if transform is provided. 如果提供变换返回true。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, BlueprintNativeEvent, BlueprintPure=false, Category="GCS|Weapon", meta=(ExpandBoolAsExecs="ReturnValue"))
|
||||
bool GetRelativeTransformToSocket(const USkeletalMeshComponent* InSkeletalMeshComponent, const UStaticMesh* StaticMesh, const USkeletalMesh* SkeletalMesh, FName SocketName,
|
||||
FTransform& OutTransform) const;
|
||||
};
|
||||
@@ -0,0 +1,95 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "UObject/Object.h"
|
||||
#include "GCS_CombatEnumLibrary.generated.h"
|
||||
|
||||
/**
|
||||
* Enum for basic directional inputs.
|
||||
* 基本方向输入的枚举。
|
||||
*/
|
||||
UENUM(BlueprintType)
|
||||
enum class EGCS_Direction : uint8
|
||||
{
|
||||
/**
|
||||
* Forward direction.
|
||||
* 前进方向。
|
||||
*/
|
||||
Forward,
|
||||
|
||||
/**
|
||||
* Backward direction.
|
||||
* 后退方向。
|
||||
*/
|
||||
Backward,
|
||||
|
||||
/**
|
||||
* Left direction.
|
||||
* 左方向。
|
||||
*/
|
||||
Left,
|
||||
|
||||
/**
|
||||
* Right direction.
|
||||
* 右方向。
|
||||
*/
|
||||
Right
|
||||
};
|
||||
|
||||
/**
|
||||
* Enum for eight-directional inputs.
|
||||
* 八方向输入的枚举。
|
||||
*/
|
||||
UENUM(BlueprintType)
|
||||
enum class EGCS_Direction_8 : uint8
|
||||
{
|
||||
/**
|
||||
* Forward direction.
|
||||
* 前进方向。
|
||||
*/
|
||||
Forward,
|
||||
|
||||
/**
|
||||
* Forward-left direction.
|
||||
* 前左方向。
|
||||
*/
|
||||
Forward_Left,
|
||||
|
||||
/**
|
||||
* Forward-right direction.
|
||||
* 前右方向。
|
||||
*/
|
||||
Forward_Right,
|
||||
|
||||
/**
|
||||
* Backward direction.
|
||||
* 后退方向。
|
||||
*/
|
||||
Backward,
|
||||
|
||||
/**
|
||||
* Backward-left direction.
|
||||
* 后左方向。
|
||||
*/
|
||||
Backward_Left,
|
||||
|
||||
/**
|
||||
* Backward-right direction.
|
||||
* 后右方向。
|
||||
*/
|
||||
Backward_Right,
|
||||
|
||||
/**
|
||||
* Left direction.
|
||||
* 左方向。
|
||||
*/
|
||||
Left,
|
||||
|
||||
/**
|
||||
* Right direction.
|
||||
* 右方向。
|
||||
*/
|
||||
Right
|
||||
};
|
||||
@@ -0,0 +1,230 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "GameplayEffect.h"
|
||||
#include "GameplayTagContainer.h"
|
||||
#include "StructUtils/InstancedStruct.h"
|
||||
#include "UObject/Object.h"
|
||||
#include "GCS_CombatStructLibrary.generated.h"
|
||||
|
||||
class UTargetingPreset;
|
||||
class UAnimMontage;
|
||||
|
||||
/**
|
||||
* Structure for tagged value pairs.
|
||||
* 标记值对的结构。
|
||||
*/
|
||||
USTRUCT(BlueprintType)
|
||||
struct GENERICCOMBATSYSTEM_API FGCS_TaggedValue
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
/**
|
||||
* The gameplay tag for the attribute.
|
||||
* 属性的游戏标签。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GCS")
|
||||
FGameplayTag Attribute;
|
||||
|
||||
/**
|
||||
* The value applied to the attribute.
|
||||
* 应用于属性的值。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GCS")
|
||||
float Value{0};
|
||||
};
|
||||
|
||||
USTRUCT(BlueprintType, meta=(Hidden))
|
||||
struct GENERICCOMBATSYSTEM_API FGCS_AbilityActionExtension
|
||||
{
|
||||
GENERATED_BODY()
|
||||
};
|
||||
|
||||
/**
|
||||
* Structure for ability actions.
|
||||
* 能力动作的结构。
|
||||
*/
|
||||
USTRUCT(BlueprintType)
|
||||
struct GENERICCOMBATSYSTEM_API FGCS_AbilityAction
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
/**
|
||||
* The animation montage to play.
|
||||
* 要播放的动画蒙太奇。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Animation")
|
||||
TObjectPtr<UAnimMontage> Animation;
|
||||
|
||||
/**
|
||||
* The playback rate for the montage.
|
||||
* 蒙太奇的播放速率。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Animation")
|
||||
float PlayRate{1.f};
|
||||
|
||||
/**
|
||||
* The starting section name for the montage.
|
||||
* 蒙太奇的起始片段名称。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Animation")
|
||||
FName StartSection{NAME_None};
|
||||
|
||||
/**
|
||||
* Whether to stop the montage when the ability ends.
|
||||
* 能力结束时是否停止蒙太奇。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Animation")
|
||||
bool bStopWhenAbilityEnds{true};
|
||||
|
||||
/**
|
||||
* Indicate if the selected anim sequence has root motion enabled.(It was auto calculated during save.)
|
||||
* 标识选择的动画序列是启用了根运动。(保存时自动设置。)
|
||||
*/
|
||||
UPROPERTY(VisibleAnywhere, Category = "Animation", Meta=(EditCondition=False, EditConditionHides))
|
||||
bool bHasRootMotion{false};
|
||||
|
||||
/**
|
||||
* Scale for animation root motion translation.
|
||||
* 动画根运动平移的缩放。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Animation")
|
||||
float AnimRootMotionTranslationScale{1.f};
|
||||
|
||||
/**
|
||||
* Start time for the montage in seconds.
|
||||
* 蒙太奇的起始时间(秒)。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Animation")
|
||||
float StartTimeSeconds{0.f};
|
||||
|
||||
/**
|
||||
* Whether to allow interruption after blend out.
|
||||
* 是否允许在混合结束时中断。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Animation")
|
||||
bool bAllowInterruptAfterBlendOut{false};
|
||||
|
||||
/**
|
||||
* Gameplay effect for ability cost.
|
||||
* 能力消耗的游戏效果。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "GameplayEffects")
|
||||
TSubclassOf<UGameplayEffect> CostGameplayEffect;
|
||||
|
||||
/**
|
||||
* Allowing C++ users to add custom fields to ability action.
|
||||
* 允许C++用户添加自定义字段到AbilityAction。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Extension")
|
||||
TInstancedStruct<FGCS_AbilityActionExtension> Extension;
|
||||
|
||||
#if WITH_EDITORONLY_DATA
|
||||
/**
|
||||
* Editor-friendly name for the ability action.
|
||||
* 能力动作的编辑器友好名称。
|
||||
*/
|
||||
UPROPERTY(VisibleAnywhere, Category=AlwaysHidden, Meta=(EditCondition=False, EditConditionHides))
|
||||
FString EditorFriendlyName;
|
||||
#endif
|
||||
};
|
||||
|
||||
/**
|
||||
* Structure for ability actions with tag queries.
|
||||
* 带有标签查询的能力动作结构。
|
||||
*/
|
||||
USTRUCT(BlueprintType)
|
||||
struct GENERICCOMBATSYSTEM_API FGCS_AbilityActionsWithQuery
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
/**
|
||||
* Source tag query for filtering.
|
||||
* 用于过滤的来源标签查询。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "GCS")
|
||||
FGameplayTagQuery SourceTagQuery;
|
||||
|
||||
/**
|
||||
* Target tag query for filtering.
|
||||
* 用于过滤的目标标签查询。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "GCS")
|
||||
FGameplayTagQuery TargetTagQuery;
|
||||
|
||||
/**
|
||||
* Array of ability actions.
|
||||
* 能力动作数组。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "GCS", meta=(TitleProperty="EditorFriendlyName"))
|
||||
TArray<FGCS_AbilityAction> Actions;
|
||||
|
||||
#if WITH_EDITORONLY_DATA
|
||||
/**
|
||||
* Editor-friendly name for the action set.
|
||||
* 动作集的编辑器友好名称。
|
||||
*/
|
||||
UPROPERTY(VisibleAnywhere, Category=AlwaysHidden, Meta=(EditCondition=False, EditConditionHides))
|
||||
FString EditorFriendlyName;
|
||||
#endif
|
||||
};
|
||||
|
||||
/**
|
||||
* Structure for ability action sets.
|
||||
* 能力动作集的结构。
|
||||
*/
|
||||
USTRUCT(BlueprintType)
|
||||
struct GENERICCOMBATSYSTEM_API FGCS_AbilityActionSet
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
/**
|
||||
* The gameplay tag for the ability.
|
||||
* 能力的游戏标签。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "GCS")
|
||||
FGameplayTag AbilityTag;
|
||||
|
||||
/**
|
||||
* Array of ability actions.
|
||||
* 能力动作数组。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "GCS", meta=(TitleProperty="EditorFriendlyName"))
|
||||
TArray<FGCS_AbilityAction> Actions;
|
||||
|
||||
/**
|
||||
* Layered action sets for conditional selection based on tags.
|
||||
* 基于标签条件选择的层次动作集。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "GCS", meta=(TitleProperty="EditorFriendlyName"))
|
||||
TArray<FGCS_AbilityActionsWithQuery> Layered;
|
||||
};
|
||||
|
||||
/**
|
||||
* Base structure for user settings.
|
||||
* 用户设置的基结构。
|
||||
*/
|
||||
USTRUCT(BlueprintType)
|
||||
struct UE_DEPRECATED(1.5, "Using Extension field insted of this one.") GENERICCOMBATSYSTEM_API FGCS_UserSetting
|
||||
{
|
||||
GENERATED_BODY()
|
||||
};
|
||||
|
||||
/**
|
||||
* User settings structure for tag-to-float mappings.
|
||||
* 标签到浮点映射的用户设置结构。
|
||||
*/
|
||||
USTRUCT(BlueprintType)
|
||||
struct UE_DEPRECATED(1.5, "this sample also deprecated due to FGCS_UserSetting was deprecated.") FGCS_UserSetting_Attributes : public FGCS_UserSetting
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
/**
|
||||
* Map of gameplay tags to float attributes.
|
||||
* 游戏标签到浮点属性的映射。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="UserSettings")
|
||||
TMap<FGameplayTag, float> Attributes;
|
||||
};
|
||||
@@ -0,0 +1,411 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "GGA_AbilitySystemGlobals.h"
|
||||
#include "CombatFlow/GCS_AttackResult.h"
|
||||
#include "GCS_CombatSystemComponent.generated.h"
|
||||
|
||||
class UGCS_CombatFlow;
|
||||
|
||||
/**
|
||||
* Structure for requesting montage playback.
|
||||
* 请求蒙太奇播放的结构。
|
||||
*/
|
||||
USTRUCT(BlueprintType)
|
||||
struct GENERICCOMBATSYSTEM_API FGCS_PlayMontageRequest
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
/**
|
||||
* The animation montage to play.
|
||||
* 要播放的动画蒙太奇。
|
||||
*/
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="GCS")
|
||||
TObjectPtr<UAnimMontage> AnimMontage{nullptr};
|
||||
|
||||
/**
|
||||
* The playback rate for the montage.
|
||||
* 蒙太奇的播放速率。
|
||||
*/
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="GCS")
|
||||
float PlayRate{1.0f};
|
||||
|
||||
/**
|
||||
* The starting section name for the montage.
|
||||
* 蒙太奇的起始片段名称。
|
||||
*/
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="GCS")
|
||||
FName StartSectionName{NAME_None};
|
||||
|
||||
/**
|
||||
* The scale for root motion translation.
|
||||
* 根运动平移的缩放。
|
||||
*/
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="GCS")
|
||||
float RootTranslationScale{1.0f};
|
||||
|
||||
/**
|
||||
* The start time for the montage in seconds.
|
||||
* 蒙太奇的起始时间(秒)。
|
||||
*/
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="GCS")
|
||||
float StartTimeSeconds{0.0f};
|
||||
};
|
||||
|
||||
/**
|
||||
* Structure for predicted montage information.
|
||||
* 预测蒙太奇信息的结构。
|
||||
*/
|
||||
USTRUCT()
|
||||
struct FGCS_PredictedMontageInfo
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
/**
|
||||
* The animation montage.
|
||||
* 动画蒙太奇。
|
||||
*/
|
||||
UPROPERTY()
|
||||
TObjectPtr<UAnimMontage> AnimMontage{nullptr};
|
||||
|
||||
/**
|
||||
* The playback rate.
|
||||
* 播放速率。
|
||||
*/
|
||||
UPROPERTY()
|
||||
float PlayRate{1.0f};
|
||||
|
||||
/**
|
||||
* The starting section name.
|
||||
* 起始片段名称。
|
||||
*/
|
||||
UPROPERTY()
|
||||
FName StartSectionName{NAME_None};
|
||||
|
||||
/**
|
||||
* The time the montage was triggered.
|
||||
* 蒙太奇触发的时间。
|
||||
*/
|
||||
UPROPERTY()
|
||||
float TriggeredTime{0.0f};
|
||||
};
|
||||
|
||||
/**
|
||||
* Structure for replicated montage information.
|
||||
* 复制蒙太奇信息的结构。
|
||||
*/
|
||||
USTRUCT()
|
||||
struct FGCS_ReplicatedMontageInfo
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
/**
|
||||
* The animation montage.
|
||||
* 动画蒙太奇。
|
||||
*/
|
||||
UPROPERTY()
|
||||
TObjectPtr<UAnimMontage> AnimMontage{nullptr};
|
||||
|
||||
/**
|
||||
* The playback rate.
|
||||
* 播放速率。
|
||||
*/
|
||||
UPROPERTY()
|
||||
float PlayRate{1.0f};
|
||||
|
||||
/**
|
||||
* The starting section name.
|
||||
* 起始片段名称。
|
||||
*/
|
||||
UPROPERTY()
|
||||
FName StartSectionName{NAME_None};
|
||||
|
||||
/**
|
||||
* The time the montage was triggered.
|
||||
* 蒙太奇触发的时间。
|
||||
*/
|
||||
UPROPERTY()
|
||||
float TriggeredTime{0.0f};
|
||||
};
|
||||
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FGCS_ComboStepChangedEventSignature, int32, PrevComboStep);
|
||||
|
||||
|
||||
/**
|
||||
* Component for handling offensive and defensive combat behaviors.
|
||||
* 处理进攻和防御战斗行为的组件。
|
||||
*/
|
||||
UCLASS(ClassGroup=GCS, Blueprintable, BlueprintType, AutoExpandCategories=("GCS"), meta=(BlueprintSpawnableComponent))
|
||||
class GENERICCOMBATSYSTEM_API UGCS_CombatSystemComponent : public UActorComponent, public IGGA_AbilitySystemGlobalsEventReceiver
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
friend UGCS_CombatFlow;
|
||||
|
||||
public:
|
||||
/**
|
||||
* Default constructor.
|
||||
* 默认构造函数。
|
||||
*/
|
||||
UGCS_CombatSystemComponent();
|
||||
|
||||
/**
|
||||
* Initializes the component.
|
||||
* 初始化组件。
|
||||
*/
|
||||
virtual void InitializeComponent() override;
|
||||
|
||||
/**
|
||||
* Called when the game starts.
|
||||
* 游戏开始时调用。
|
||||
*/
|
||||
virtual void BeginPlay() override;
|
||||
|
||||
/**
|
||||
* Called when the game ends.
|
||||
* 游戏结束时调用。
|
||||
* @param EndPlayReason The reason for ending. 结束原因。
|
||||
*/
|
||||
virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override;
|
||||
|
||||
/**
|
||||
* Retrieves lifetime replicated properties.
|
||||
* 获取生命周期复制属性。
|
||||
* @param OutLifetimeProps The lifetime properties. 生命周期属性。
|
||||
*/
|
||||
virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override;
|
||||
|
||||
/**
|
||||
* Gets the combat system component for an actor.
|
||||
* 获取演员的战斗系统组件。
|
||||
* @param Actor The actor to query. 要查询的演员。
|
||||
* @return The combat system component. 战斗系统组件。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "GCS|Combat", Meta = (DefaultToSelf="Actor"))
|
||||
static UGCS_CombatSystemComponent* GetCombatSystemComponent(const AActor* Actor);
|
||||
|
||||
/**
|
||||
* Finds the combat system component for an actor.
|
||||
* 查找演员的战斗系统组件。
|
||||
* @param Actor The actor to query. 要查询的演员。
|
||||
* @param CombatComponent The found component (output). 找到的组件(输出)。
|
||||
* @return True if found. 如果找到返回true。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "GCS|Combat", Meta = (DefaultToSelf="Actor", ExpandBoolAsExecs = "ReturnValue"))
|
||||
static bool FindCombatSystemComponent(const AActor* Actor, UGCS_CombatSystemComponent*& CombatComponent);
|
||||
|
||||
/**
|
||||
* Finds a typed combat system component for an actor.
|
||||
* 查找演员的特定类型战斗系统组件。
|
||||
* @param Actor The actor to query. 要查询的演员。
|
||||
* @param DesiredClass The desired component class. 期望的组件类。
|
||||
* @param Component The found component (output). 找到的组件(输出)。
|
||||
* @return True if found. 如果找到返回true。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "GCS|Combat", meta=(DefaultToSelf="Actor", DeterminesOutputType="DesiredClass", DynamicOutputParam="Component", ExpandBoolAsExecs="ReturnValue"))
|
||||
static bool FindTypedCombatSystemComponent(AActor* Actor, TSubclassOf<UGCS_CombatSystemComponent> DesiredClass, UGCS_CombatSystemComponent*& Component);
|
||||
|
||||
/**
|
||||
* Gets the combat flow for handling incoming attacks.
|
||||
* 获取处理传入攻击的战斗流程。
|
||||
* @return The combat flow instance. 战斗流程实例。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "GCS|Combat", meta=(DisplayName="Get Combat Flow"))
|
||||
UGCS_CombatFlow* GetCombatFlow() const;
|
||||
|
||||
/**
|
||||
* Registers an attack result.
|
||||
* 注册攻击结果。
|
||||
* @param Payload The attack result to register. 要注册的攻击结果。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category="GCS|Combat")
|
||||
void RegisterAttackResult(UPARAM(ref)
|
||||
FGCS_AttackResult& Payload);
|
||||
|
||||
/**
|
||||
* Gets the last processed attack result.
|
||||
* 获取最后处理的攻击结果。
|
||||
* @return The last attack result. 最后攻击结果。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "GCS|Combat")
|
||||
FGCS_AttackResult GetLastProcessedAttackResult() const;
|
||||
|
||||
/**
|
||||
* Sets the last processed attack result.
|
||||
* 设置最后处理的攻击结果。
|
||||
* @param Payload The attack result to set. 要设置的攻击结果。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "GCS|Combat")
|
||||
void SetLastProcessedAttackResult(const FGCS_AttackResult& Payload);
|
||||
|
||||
/**
|
||||
* Plays a predictable montage for a target combat system component.
|
||||
* 为目标战斗系统组件播放可预测的蒙太奇。
|
||||
* @param TargetCSC The target combat system component. 目标战斗系统组件。
|
||||
* @param Request The montage play request. 蒙太奇播放请求。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category="GCS|Combat")
|
||||
void PlayPredictableMontageForTarget(UGCS_CombatSystemComponent* TargetCSC, FGCS_PlayMontageRequest Request);
|
||||
|
||||
/**
|
||||
* Server RPC to play a predictable montage for a target.
|
||||
* 为目标播放可预测蒙太奇的服务器RPC。
|
||||
* @param TargetCSC The target combat system component. 目标战斗系统组件。
|
||||
* @param Request The montage play request. 蒙太奇播放请求。
|
||||
*/
|
||||
UFUNCTION(Server, Reliable, BlueprintCallable, Category="GCS|Combat")
|
||||
void ServerPlayPredictableMontageForTarget(UGCS_CombatSystemComponent* TargetCSC, FGCS_PlayMontageRequest Request);
|
||||
|
||||
/**
|
||||
* Sets the replicated montage information.
|
||||
* 设置复制的蒙太奇信息。
|
||||
* @param Request The montage play request. 蒙太奇播放请求。
|
||||
*/
|
||||
void SetReplicatedMontage(const FGCS_PlayMontageRequest& Request);
|
||||
|
||||
/**
|
||||
* Timer handle for montage-related operations.
|
||||
* 蒙太奇相关操作的计时器句柄。
|
||||
*/
|
||||
FTimerHandle TimerHandle;
|
||||
|
||||
/**
|
||||
* Handles replication of montage information.
|
||||
* 处理蒙太奇信息的复制。
|
||||
*/
|
||||
UFUNCTION()
|
||||
void OnRep_ReplicatedMontageInfo();
|
||||
|
||||
/**
|
||||
* Plays a predicted montage.
|
||||
* 播放预测的蒙太奇。
|
||||
* @param Request The montage play request. 蒙太奇播放请求。
|
||||
*/
|
||||
void PlayPredictedMontage(const FGCS_PlayMontageRequest& Request);
|
||||
|
||||
/**
|
||||
* Gets the character's skeletal mesh component.
|
||||
* 获取角色的骨骼网格组件。
|
||||
* @return The skeletal mesh component. 骨骼网格组件。
|
||||
*/
|
||||
USkeletalMeshComponent* GetCharacterMeshComponent() const;
|
||||
|
||||
protected:
|
||||
/**
|
||||
* Handles pre-gameplay effect spec application.
|
||||
* 处理游戏效果规格应用前逻辑。
|
||||
* @param Spec The gameplay effect spec. 游戏效果规格。
|
||||
* @param AbilitySystemComponent The ability system component. 能力系统组件。
|
||||
*/
|
||||
virtual void OnGlobalPreGameplayEffectSpecApply(FGameplayEffectSpec& Spec, UAbilitySystemComponent* AbilitySystemComponent) override;
|
||||
|
||||
/**
|
||||
* Handles replication of the combat flow.
|
||||
* 处理战斗流程的复制。
|
||||
*/
|
||||
UFUNCTION()
|
||||
void OnRep_CombatFlow();
|
||||
|
||||
/**
|
||||
* The class of the combat flow to instantiate.
|
||||
* 要实例化的战斗流程类。
|
||||
*/
|
||||
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "GCS|Combat Settings")
|
||||
TSubclassOf<UGCS_CombatFlow> CombatFlowClass;
|
||||
|
||||
/**
|
||||
* The instantiated combat flow.
|
||||
* 实例化的战斗流程。
|
||||
*/
|
||||
UPROPERTY(VisibleAnywhere, ReplicatedUsing=OnRep_CombatFlow, Category = "GCS|Combat State", meta=(ShowInnerProperties))
|
||||
TObjectPtr<UGCS_CombatFlow> CombatFlow;
|
||||
|
||||
/**
|
||||
* The last attack result processed by the combat flow.
|
||||
* 战斗流程处理的最后攻击结果。
|
||||
*/
|
||||
UPROPERTY(VisibleAnywhere, Category = "GCS|Combat State")
|
||||
FGCS_AttackResult LastProcessedAttackResult;
|
||||
|
||||
/**
|
||||
* Container for attack results.
|
||||
* 攻击结果容器。
|
||||
*/
|
||||
UPROPERTY(VisibleAnywhere, Replicated, Category="GCS|Combat State")
|
||||
FGCS_AttackResultContainer AttackResultContainer;
|
||||
|
||||
/**
|
||||
* Replicated montage information.
|
||||
* 复制的蒙太奇信息。
|
||||
*/
|
||||
UPROPERTY(VisibleAnywhere, ReplicatedUsing=OnRep_ReplicatedMontageInfo, Category = "GCS|Combat State")
|
||||
FGCS_ReplicatedMontageInfo ReplicatedMontageInfo;
|
||||
|
||||
/**
|
||||
* Predicted montage information.
|
||||
* 预测的蒙太奇信息。
|
||||
*/
|
||||
UPROPERTY(VisibleAnywhere, Category = "GCS|Combat State")
|
||||
FGCS_PredictedMontageInfo PredictedMontageInfo;
|
||||
|
||||
|
||||
#pragma region Combo System
|
||||
|
||||
public:
|
||||
/**
|
||||
* Get the current combo step.
|
||||
* @return The current combo step.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, Category="GMS|Combat")
|
||||
int32 GetComboStep() const;
|
||||
UFUNCTION(BlueprintCallable, Category="GCS|Combat")
|
||||
void UpdateComboStep(int32 NewComboStep);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category="GCS|Combat")
|
||||
virtual void ResetComboState();
|
||||
|
||||
/**
|
||||
* Event for combo step changed.
|
||||
* 连击步骤变更事件。
|
||||
*/
|
||||
UPROPERTY(BlueprintAssignable, Category="Event")
|
||||
FGCS_ComboStepChangedEventSignature OnComboStepChangedEvent;
|
||||
|
||||
private:
|
||||
void UpdateComboStep(int32 NewComboStep, bool bSendRpc);
|
||||
|
||||
UFUNCTION()
|
||||
void OnReplicated_ComboStep(int32 PrevComboStep);
|
||||
|
||||
/**
|
||||
* Client RPC to set the combo step.
|
||||
* 客户端RPC设置运动集。
|
||||
* @param NewComboStep combo step. 新运动集。
|
||||
*/
|
||||
UFUNCTION(Client, Reliable, WithValidation)
|
||||
void ClientUpdateComboStep(int32 NewComboStep);
|
||||
|
||||
/**
|
||||
* Server RPC to set the combo step.
|
||||
* 服务器RPC设置运动集。
|
||||
* @param NewComboStep The new combo step. 新运动集。
|
||||
*/
|
||||
UFUNCTION(Server, Reliable, WithValidation)
|
||||
void ServerUpdateComboStep(int32 NewComboStep);
|
||||
|
||||
protected:
|
||||
virtual bool ClientUpdateComboStep_Validate(int32 NewComboStep);
|
||||
virtual bool ServerUpdateComboStep_Validate(int32 NewComboStep);
|
||||
|
||||
UFUNCTION(BlueprintNativeEvent, Category="GMS|Combat")
|
||||
void OnComboStepChanged(int32 PrevComboStep);
|
||||
virtual void OnComboStepChanged_Implementation(int32 PrevComboStep);
|
||||
|
||||
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, ReplicatedUsing=OnReplicated_ComboStep, Category = "GCS|Combat State")
|
||||
int32 ComboStep{0};
|
||||
|
||||
private:
|
||||
#pragma endregion
|
||||
};
|
||||
@@ -0,0 +1,40 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Engine/DeveloperSettings.h"
|
||||
#include "GCS_CombatSystemSettings.generated.h"
|
||||
|
||||
/**
|
||||
* Settings for the combat system.
|
||||
* 战斗系统的设置。
|
||||
*/
|
||||
UCLASS(Config=Game, DefaultConfig)
|
||||
class GENERICCOMBATSYSTEM_API UGCS_CombatSystemSettings : public UDeveloperSettings
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
/**
|
||||
* Gets the combat system settings instance.
|
||||
* 获取战斗系统设置实例。
|
||||
* @return The combat system settings. 战斗系统设置。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, Category="GCS")
|
||||
static const UGCS_CombatSystemSettings* Get();
|
||||
|
||||
/**
|
||||
* Tag name for querying the main skeletal mesh component.
|
||||
* 查询主要骨骼网格组件的标签名称。
|
||||
*/
|
||||
UPROPERTY(Config, EditAnywhere, BlueprintReadOnly, NoClear, Category="Common", meta=(DisplayName="Main Mesh Lookup Tag Name"))
|
||||
FName CharacterMeshLookupTag{TEXT("Main")};
|
||||
|
||||
/**
|
||||
* Disables affiliation checks for debugging (allows cross-team damage).
|
||||
* 禁用归属检查以进行调试(允许跨队伍伤害)。
|
||||
*/
|
||||
UPROPERTY(Config, EditAnywhere, BlueprintReadOnly, NoClear, Category="Debug")
|
||||
bool bDisableAffiliationCheck{false};
|
||||
};
|
||||
@@ -0,0 +1,105 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "GameplayEffectTypes.h"
|
||||
#include "GameplayTagContainer.h"
|
||||
#include "GGA_AbilitySystemStructLibrary.h"
|
||||
#include "UObject/Interface.h"
|
||||
#include "GCS_EffectCauserInterface.generated.h"
|
||||
|
||||
class UGameplayEffect;
|
||||
|
||||
/**
|
||||
* Interface for objects that cause gameplay effects based on combat impact.
|
||||
* 基于战斗影响产生游戏效果的对象的接口。
|
||||
*/
|
||||
UINTERFACE(MinimalAPI, BlueprintType, Blueprintable)
|
||||
class UGCS_EffectCauserInterface : public UInterface
|
||||
{
|
||||
GENERATED_BODY()
|
||||
};
|
||||
|
||||
/**
|
||||
* Interface for effect causers (e.g., bullets, weapons, traps).
|
||||
* 效果触发者(例如子弹、武器、陷阱)的接口。
|
||||
*/
|
||||
class GENERICCOMBATSYSTEM_API IGCS_EffectCauserInterface
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
/**
|
||||
* Gets a pre-existing gameplay effect spec handle.
|
||||
* 获取预存在的游戏效果规格句柄。
|
||||
* @param OutHandle The effect spec handle (output). 效果规格句柄(输出)。
|
||||
* @return True if a handle is provided. 如果提供句柄返回true。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category="GCS|Combat")
|
||||
bool GetEffectSpecHandle(FGameplayEffectSpecHandle& OutHandle);
|
||||
virtual bool GetEffectSpecHandle_Implementation(FGameplayEffectSpecHandle& OutHandle) = 0;
|
||||
|
||||
/**
|
||||
* Gets the effect container.
|
||||
* 获取效果容器。
|
||||
* @return The effect container. 效果容器。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category="GCS|Combat")
|
||||
FGGA_GameplayEffectContainer GetEffectContainer();
|
||||
virtual FGGA_GameplayEffectContainer GetEffectContainer_Implementation() const = 0;
|
||||
|
||||
/**
|
||||
* Gets the effect container level override.
|
||||
* 获取效果容器等级覆盖。
|
||||
* @return The level override. 等级覆盖。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category="GCS|Combat")
|
||||
int32 GetEffectContainerLevelOverride() const;
|
||||
virtual int32 GetEffectContainerLevelOverride_Implementation() const = 0;
|
||||
|
||||
/**
|
||||
* Sets the effect container spec.
|
||||
* 设置效果容器规格。
|
||||
* @param InEffectContainerSpec The effect container spec. 效果容器规格。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category="GCS|Combat")
|
||||
void SetEffectContainerSpec(const FGGA_GameplayEffectContainerSpec& InEffectContainerSpec);
|
||||
virtual void SetEffectContainerSpec_Implementation(const FGGA_GameplayEffectContainerSpec& InEffectContainerSpec) = 0;
|
||||
|
||||
/**
|
||||
* Gets the effect container spec.
|
||||
* 获取效果容器规格。
|
||||
* @return The effect container spec. 效果容器规格。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category="GCS|Combat")
|
||||
FGGA_GameplayEffectContainerSpec GetEffectContainerSpec() const;
|
||||
virtual FGGA_GameplayEffectContainerSpec GetEffectContainerSpec_Implementation() const = 0;
|
||||
|
||||
/**
|
||||
* Gets the gameplay effect class.
|
||||
* 获取游戏效果类。
|
||||
* @return The gameplay effect class. 游戏效果类。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category="GCS|Combat")
|
||||
TSubclassOf<UGameplayEffect> GetEffectClass() const;
|
||||
virtual TSubclassOf<UGameplayEffect> GetEffectClass_Implementation() const = 0;
|
||||
|
||||
/**
|
||||
* Gets the effect level.
|
||||
* 获取效果等级。
|
||||
* @return The effect level. 效果等级。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category="GCS|Combat")
|
||||
int32 GetEffectLevel() const;
|
||||
virtual int32 GetEffectLevel_Implementation() const = 0;
|
||||
|
||||
/**
|
||||
* Sets the gameplay effect spec for later use.
|
||||
* 设置游戏效果规格以供后续使用。
|
||||
* @param InEffectSpec The effect spec to set. 要设置的效果规格。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category="GCS|Combat")
|
||||
void SetEffectSpec(UPARAM(ref) FGameplayEffectSpecHandle& InEffectSpec);
|
||||
virtual void SetEffectSpec_Implementation(FGameplayEffectSpecHandle& InEffectSpec) = 0;
|
||||
};
|
||||
@@ -0,0 +1,14 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "UObject/Object.h"
|
||||
#include "NativeGameplayTags.h"
|
||||
|
||||
namespace GCS_BulletLaunch
|
||||
{
|
||||
GENERICCOMBATSYSTEM_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(Always)
|
||||
GENERICCOMBATSYSTEM_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(DidNotHitPawn)
|
||||
GENERICCOMBATSYSTEM_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(HitPawn)
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "UObject/Object.h"
|
||||
|
||||
DECLARE_STATS_GROUP(TEXT("GCS"), STATGROUP_GCS, STATCAT_Advanced)
|
||||
|
||||
GENERICCOMBATSYSTEM_API DECLARE_LOG_CATEGORY_EXTERN(LogGCS, Log, All)
|
||||
|
||||
GENERICCOMBATSYSTEM_API DECLARE_LOG_CATEGORY_EXTERN(LogGCS_Targeting, Log, All)
|
||||
|
||||
GENERICCOMBATSYSTEM_API DECLARE_LOG_CATEGORY_EXTERN(LogGCS_Collision, Log, All)
|
||||
|
||||
GENERICCOMBATSYSTEM_API DECLARE_LOG_CATEGORY_EXTERN(LogGCS_Trace, Log, All)
|
||||
|
||||
|
||||
/**
|
||||
* Gets the context string for logging purposes.
|
||||
* 获取用于日志记录的上下文字符串。
|
||||
* @param ContextObject The object providing the context (optional). 提供上下文的对象(可选)。
|
||||
* @return The context string. 上下文字符串。
|
||||
*/
|
||||
GENERICCOMBATSYSTEM_API FString GetGCSLogContextString(const UObject* ContextObject = nullptr);
|
||||
|
||||
GENERICCOMBATSYSTEM_API FString GetClientServerContextString(UObject* ContextObject = nullptr);
|
||||
|
||||
#define GCS_LOG(Verbosity, Format, ...) \
|
||||
{ \
|
||||
UE_LOG(LogGCS, Verbosity, TEXT("%S: %s"),__FUNCTION__, *FString::Printf(TEXT(Format), ##__VA_ARGS__)) \
|
||||
}
|
||||
|
||||
#define GCS_CLOG(Verbosity, Format, ...) \
|
||||
{ \
|
||||
UE_LOG(LogGCS, Verbosity, TEXT("%S: ctx(%s) %s"),__FUNCTION__, *GetGCSLogContextString(this), *FString::Printf(TEXT(Format), ##__VA_ARGS__)) \
|
||||
}
|
||||
|
||||
#define GCS_CLOG_Trace(Verbosity, Format, ...) \
|
||||
{ \
|
||||
UE_LOG(LogGCS, Verbosity, TEXT("%S: ctx(%s) %s"),__FUNCTION__, *GetGCSLogContextString(this), *FString::Printf(TEXT(Format), ##__VA_ARGS__)) \
|
||||
}
|
||||
|
||||
#define GCS_OWNED_CLOG(LogOwner,Verbosity, Format, ...) \
|
||||
{ \
|
||||
UE_LOG(LogGCS, Verbosity, TEXT("%S: ctx(%s) %s"),__FUNCTION__, *GetGCSLogContextString(LogOwner), *FString::Printf(TEXT(Format), ##__VA_ARGS__)) \
|
||||
}
|
||||
|
||||
|
||||
#define GCS_VLOG(Verbosity, Format, ...) UE_VLOG(GetOwner(), LogGAIS_Command, Verbosity, Format, ##__VA_ARGS__)
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user