Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
124 changes: 118 additions & 6 deletions Source/GMCAbilitySystem/Private/Components/GMCAbilityComponent.cpp
Copy link
Collaborator

@Aherys Aherys Sep 30, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion :

  • Backward incrementation removal instead of dual loop.
  • int reference as primitive type is less performant than copy (the more you know)

Original file line number Diff line number Diff line change
Expand Up @@ -806,6 +806,21 @@ void UGMC_AbilitySystemComponent::TickActiveEffects(float DeltaTime)
ActiveEffects.Remove(EffectID);
ActiveEffectsData.RemoveAll([EffectID](const FGMCAbilityEffectData& EffectData) {return EffectData.EffectID == EffectID;});
}

// Clean effect handles
TArray<int> ExpiredHandles;
for (auto& [ID, HandleData] : EffectHandles)
{
if (HandleData.NetworkId > 0 && !ActiveEffects.Contains(HandleData.NetworkId))
{
ExpiredHandles.Add(HandleData.Handle);
}
}
for (const int& Handle : ExpiredHandles)
{
EffectHandles.Remove(Handle);
}

}

void UGMC_AbilitySystemComponent::TickActiveAbilities(float DeltaTime)
Expand Down Expand Up @@ -1190,6 +1205,16 @@ UGMCAbilityEffect* UGMC_AbilitySystemComponent::ProcessEffectOperation(
}

ApplyAbilityEffect(Effect, EffectData);

for (auto& [EffectHandle, EffectHandleData] : EffectHandles)
{
// If we don't already have a known effect ID, attach it to our handle now.
if (EffectHandleData.NetworkId <= 0 && EffectHandleData.OperationId == Operation.Header.OperationId)
{
EffectHandleData.NetworkId = Effect->EffectData.EffectID;
}
}

return Effect;
}

Expand Down Expand Up @@ -1368,18 +1393,75 @@ UGMCAbilityEffect* UGMC_AbilitySystemComponent::ApplyAbilityEffect(TSubclassOf<U
return ProcessEffectOperation(Operation);
}

int32 UGMC_AbilitySystemComponent::GetNextAvailableEffectHandle() const
{
if (ActionTimer == 0)
{
UE_LOG(LogGMCAbilitySystem, Error, TEXT("[ApplyAbilityEffect] Action Timer is 0, cannot generate Effect ID. Is it a listen server smoothed pawn?"));
return -1;
}

int NewEffectHandle = static_cast<int>(ActionTimer * 100);
while (EffectHandles.Contains(NewEffectHandle))
{
NewEffectHandle++;
}

return NewEffectHandle;
}

void UGMC_AbilitySystemComponent::GetEffectFromHandle_BP(int EffectHandle, bool& bOutSuccess, int32& OutEffectNetworkId,
UGMCAbilityEffect*& OutEffect)
{
bOutSuccess = GetEffectFromHandle(EffectHandle, OutEffectNetworkId, OutEffect);
}

bool UGMC_AbilitySystemComponent::GetEffectFromHandle(int EffectHandle, int32& OutEffectNetworkId,
UGMCAbilityEffect*& OutEffect) const
{
FGMASQueueOperationHandle HandleData;

if (!GetEffectHandle(EffectHandle, HandleData)) return false;

OutEffectNetworkId = HandleData.NetworkId;
if (HandleData.NetworkId > 0)
{
OutEffect = ActiveEffects[HandleData.NetworkId];
}
return true;
}

bool UGMC_AbilitySystemComponent::GetEffectHandle(int EffectHandle, FGMASQueueOperationHandle& HandleData) const
{
for (auto& [ID, Handle] : EffectHandles)
{
if (Handle.Handle == EffectHandle)
{
HandleData = Handle;
return true;
}
}
return false;
}

void UGMC_AbilitySystemComponent::RemoveEffectHandle(int EffectHandle)
{
EffectHandles.Remove(EffectHandle);
}

void UGMC_AbilitySystemComponent::ApplyAbilityEffectSafe(TSubclassOf<UGMCAbilityEffect> EffectClass,
FGMCAbilityEffectData InitializationData, EGMCAbilityEffectQueueType QueueType, bool& OutSuccess, int& OutEffectId,
UGMCAbilityEffect*& OutEffect)
FGMCAbilityEffectData InitializationData, EGMCAbilityEffectQueueType QueueType, bool& OutSuccess, int& OutEffectHandle, int& OutEffectId,
UGMCAbilityEffect*& OutEffect)
{
OutSuccess = ApplyAbilityEffect(EffectClass, InitializationData, QueueType, OutEffectId, OutEffect);
OutSuccess = ApplyAbilityEffect(EffectClass, InitializationData, QueueType, OutEffectHandle, OutEffectId, OutEffect);
}

bool UGMC_AbilitySystemComponent::ApplyAbilityEffect(TSubclassOf<UGMCAbilityEffect> EffectClass,
FGMCAbilityEffectData InitializationData, EGMCAbilityEffectQueueType QueueType, int& OutEffectId, UGMCAbilityEffect*& OutEffect)
FGMCAbilityEffectData InitializationData, EGMCAbilityEffectQueueType QueueType, int& OutEffectHandle, int& OutEffectId, UGMCAbilityEffect*& OutEffect)
{
OutEffect = nullptr;
OutEffectId = -1;
OutEffectHandle = -1;
if (EffectClass == nullptr)
{
UE_LOG(LogGMCAbilitySystem, Error, TEXT("Trying to apply Effect, but effect is null!"));
Expand All @@ -1396,6 +1478,12 @@ bool UGMC_AbilitySystemComponent::ApplyAbilityEffect(TSubclassOf<UGMCAbilityEffe
*GetNetRoleAsString(GetOwnerRole()), *GetOwner()->GetName(), *EffectClass->GetName())
return false;
}

FGMASQueueOperationHandle HandleData;
HandleData.Handle = GetNextAvailableEffectHandle();
HandleData.NetworkId = EffectID;
HandleData.OperationId = Operation.Header.OperationId;
EffectHandles.Add(HandleData.Handle, HandleData);

switch(QueueType)
{
Expand All @@ -1411,12 +1499,23 @@ bool UGMC_AbilitySystemComponent::ApplyAbilityEffect(TSubclassOf<UGMCAbilityEffe
// Apply effect immediately.
OutEffect = ProcessEffectOperation(Operation);
OutEffectId = OutEffect->EffectData.EffectID;
OutEffectHandle = HandleData.Handle;
return true;
}
case EGMCAbilityEffectQueueType::PredictedQueued:
{
// We utilize the ClientAuth queue's RPC queue for the sake of convenience.
QueuedEffectOperations_ClientAuth.QueuePreparedOperation(Operation, false);
if (GMCMovementComponent->IsExecutingMove())
{
// We're in a move context, just add it directly rather than queuing.
OutEffect = ProcessEffectOperation(Operation);
OutEffectId = OutEffect->EffectData.EffectID;
}
else
{
// We utilize the ClientAuth queue's RPC queue for the sake of convenience.
QueuedEffectOperations_ClientAuth.QueuePreparedOperation(Operation, false);
}
OutEffectHandle = HandleData.Handle;
return true;
}
case EGMCAbilityEffectQueueType::ServerAuthMove:
Expand All @@ -1440,6 +1539,7 @@ bool UGMC_AbilitySystemComponent::ApplyAbilityEffect(TSubclassOf<UGMCAbilityEffe
}

OutEffectId = EffectID;
OutEffectHandle = HandleData.Handle;
return true;
}

Expand Down Expand Up @@ -1663,6 +1763,18 @@ bool UGMC_AbilitySystemComponent::RemoveEffectByIdSafe(TArray<int> Ids, EGMCAbil
return false;
}

bool UGMC_AbilitySystemComponent::RemoveEffectByHandle(int EffectHandle, EGMCAbilityEffectQueueType QueueType)
{
int32 EffectID;
UGMCAbilityEffect* Effect;
if (GetEffectFromHandle(EffectHandle, EffectID, Effect) && EffectID > 0)
{
return RemoveEffectByIdSafe({ EffectID }, QueueType);
}

return false;
}


bool UGMC_AbilitySystemComponent::RemoveEffectById(TArray<int> Ids, bool bOuterActivation) {

Expand Down
54 changes: 43 additions & 11 deletions Source/GMCAbilitySystem/Public/Components/GMCAbilityComponent.h
Original file line number Diff line number Diff line change
Expand Up @@ -42,26 +42,42 @@ struct FEffectStatePrediction
uint8 State;
};

USTRUCT()
struct FGMASQueueOperationHandle
{
GENERATED_BODY()

UPROPERTY()
int32 Handle { -1 };

UPROPERTY()
int32 OperationId { -1 };

UPROPERTY()
int32 NetworkId { -1 };
};

UENUM(BlueprintType)
enum class EGMCAbilityEffectQueueType : uint8
{
/// Immediately applied, only valid within the GMC movement cycle. Should be applied on both client and server.
Predicted UMETA(DisplayName="Predicted"),

/// Predicted effect, not replicated but will be queued for addition in the next GMC movement cycle. Valid even
/// outside of the GMC movement cycle. Should be applied on both client and server. If used during the GMC
/// movement cycle, this is silently turned into Predicted.
PredictedQueued UMETA(DisplayName="Predicted [Queued]"),

/// Only valid on server; queued from server and sent to client via RPC. Valid even outside of the GMC movement cycle.
ServerAuth UMETA(DisplayName="Server Auth"),

/// Only valid on client; queued from client and sent to the server via GMC bindings. Valid even outside of the
/// GMC movement cycle. Should PROBABLY only be used for cosmetic effects!
ClientAuth UMETA(DisplayName="Client Auth"),

/// Predicted effect, not replicated but will be queued for addition in the next GMC movement cycle. Valid even
/// outside of the GMC movement cycle. Should be applied on both client and server. WILL NOT RETURN AN EFFECT ID.
PredictedQueued UMETA(Hidden, DisplayName="ADVANCED: Predicted [Queued]"),
/// GMC movement cycle. You almost certainly don't want to use this, but it's here for the sake of completeness.
ClientAuth UMETA(Hidden, DisplayName="Client Auth"),

/// Only valid on server; queued from server and recorded in the GMC move history. Valid even outside of the GMC
/// movement cycle. Slower than ServerAuth, only use this if you really need to preserve the effect application in
/// the movement history.
/// the movement history. you almost certainly don't want to use this, but it's here for the sake of completeness.
ServerAuthMove UMETA(Hidden, DisplayName="ADVANCED: Server Auth [Movement Cycle]")
};

Expand Down Expand Up @@ -264,7 +280,7 @@ class GMCABILITYSYSTEM_API UGMC_AbilitySystemComponent : public UGameplayTasksCo
*/
UFUNCTION(BlueprintCallable, Category="GMAS|Effects", DisplayName="Apply Ability Effect")
void ApplyAbilityEffectSafe(TSubclassOf<UGMCAbilityEffect> EffectClass, FGMCAbilityEffectData InitializationData, EGMCAbilityEffectQueueType QueueType,
UPARAM(DisplayName="Success") bool& OutSuccess, UPARAM(DisplayName="Effect ID") int& OutEffectId, UPARAM(DisplayName="Effect Instance") UGMCAbilityEffect*& OutEffect);
UPARAM(DisplayName="Success") bool& OutSuccess, UPARAM(DisplayName="Effect Handle") int& OutEffectHandle, UPARAM(DisplayName="Effect Network ID") int& OutEffectId, UPARAM(DisplayName="Effect Instance") UGMCAbilityEffect*& OutEffect);

/**
* Applies an effect to the ability component. If the Queue Type is Predicted, the effect will be immediately added
Expand All @@ -274,11 +290,12 @@ class GMCABILITYSYSTEM_API UGMC_AbilitySystemComponent : public UGameplayTasksCo
* @param EffectClass The class of ability effect to add.
* @param InitializationData The initialization data for the ability effect.
* @param QueueType How to queue the effect.
* @param OutEffectId The newly-created effect's ID, if successful.
* @param OutEffect The newly-created effect instance, if a predicted add.
* @param OutEffectHandle A local handle to this effect, only valid locally.
* @param OutEffectId The newly-created effect's network ID, if one is available. Valid across server/client.
* @param OutEffect The newly-created effect instance, if available.
* @return true if the effect was applied, false otherwise.
*/
bool ApplyAbilityEffect(TSubclassOf<UGMCAbilityEffect> EffectClass, FGMCAbilityEffectData InitializationData, EGMCAbilityEffectQueueType QueueType, int& OutEffectId, UGMCAbilityEffect*& OutEffect);
bool ApplyAbilityEffect(TSubclassOf<UGMCAbilityEffect> EffectClass, FGMCAbilityEffectData InitializationData, EGMCAbilityEffectQueueType QueueType, int& OutEffectHandle, int& OutEffectId, UGMCAbilityEffect*& OutEffect);

// Do not call this directly unless you know what you are doing. Otherwise, always go through the above ApplyAbilityEffect variant!
UGMCAbilityEffect* ApplyAbilityEffect(UGMCAbilityEffect* Effect, FGMCAbilityEffectData InitializationData);
Expand Down Expand Up @@ -324,6 +341,8 @@ class GMCABILITYSYSTEM_API UGMC_AbilitySystemComponent : public UGameplayTasksCo
UFUNCTION(BlueprintCallable, Category="GMAS|Effects", DisplayName="Remove Effects by Id (Safe)")
bool RemoveEffectByIdSafe(TArray<int> Ids, EGMCAbilityEffectQueueType QueueType = EGMCAbilityEffectQueueType::Predicted);

UFUNCTION(BlueprintCallable, Category="GMAS|Effects", DisplayName="Remove Effect by Handle")
bool RemoveEffectByHandle(int EffectHandle, EGMCAbilityEffectQueueType QueueType);

/**
* Gets the number of active effects with the inputted tag.
Expand Down Expand Up @@ -596,6 +615,19 @@ class GMCABILITYSYSTEM_API UGMC_AbilitySystemComponent : public UGameplayTasksCo
UPROPERTY()
TMap<int, UGMCAbilityEffect*> ActiveEffects;

UPROPERTY()
TMap<int, FGMASQueueOperationHandle> EffectHandles;

int GetNextAvailableEffectHandle() const;

UFUNCTION(BlueprintCallable, Category="GMAS|Effects")
void GetEffectFromHandle_BP(int EffectHandle, bool& bOutSuccess, int32& OutEffectNetworkId, UGMCAbilityEffect*& OutEffect);

bool GetEffectFromHandle(int EffectHandle, int32& OutEffectNetworkId, UGMCAbilityEffect*& OutEffect) const;
bool GetEffectHandle(int EffectHandle, FGMASQueueOperationHandle& HandleData) const;

void RemoveEffectHandle(int EffectHandle);

// doesn't work ATM.
UPROPERTY(BlueprintReadOnly, Category = "GMCAbilitySystem", meta=(AllowPrivateAccess="true"))
bool bInGMCTime = false;
Expand Down