Skip to content

Instantly share code, notes, and snippets.

@cjnani16
Created March 15, 2024 03:40
Show Gist options
  • Save cjnani16/bde45622f84668b04e3c10c215e1e0da to your computer and use it in GitHub Desktop.
Save cjnani16/bde45622f84668b04e3c10c215e1e0da to your computer and use it in GitHub Desktop.
Code Sample: Procedural Ability System (Utilizing Unreal Engine's Gameplay Ability System)
// This work is the property of the Secret Dungeon Team. All rights reserved.
#include "AbilityDataFunctionLibrary.h"
#include "SecretDungeonAIEnemyBase.h"
#include "SecretDungeonCharacterBase.h"
#include "SecretDungeonPlayerState.h"
#include "SecretDungeonGASComponent.h"
bool
UAbilityDataFunctionLibrary::EvaluateSubExpression(const FString& SubExpression)
{
// Helper function to evaluate a single sub-expression
// This assumes the sub-expression contains only integers and an operator
// You can extend this logic to handle more complex expressions
TArray<FString> Tokens;
Tokens.AddZeroed(2);
if (SubExpression.Split(TEXT(">="), &Tokens[0], &Tokens[1])) {
int32 lhs = FCString::Atoi(*Tokens[0]);
int32 rhs = FCString::Atoi(*Tokens[1]);
return lhs >= rhs;
} else if (SubExpression.Split(TEXT("<="), &Tokens[0], &Tokens[1])) {
int32 lhs = FCString::Atoi(*Tokens[0]);
int32 rhs = FCString::Atoi(*Tokens[1]);
return lhs <= rhs;
} else if (SubExpression.Split(TEXT(">"), &Tokens[0], &Tokens[1])) {
int32 lhs = FCString::Atoi(*Tokens[0]);
int32 rhs = FCString::Atoi(*Tokens[1]);
return lhs > rhs;
} else if (SubExpression.Split(TEXT("<"), &Tokens[0], &Tokens[1])) {
int32 lhs = FCString::Atoi(*Tokens[0]);
int32 rhs = FCString::Atoi(*Tokens[1]);
return lhs < rhs;
} else if (SubExpression.Split(TEXT("=="), &Tokens[0], &Tokens[1])) {
int32 lhs = FCString::Atoi(*Tokens[0]);
int32 rhs = FCString::Atoi(*Tokens[1]);
return lhs == rhs;
} else if (SubExpression.Split(TEXT("!="), &Tokens[0], &Tokens[1])) {
int32 lhs = FCString::Atoi(*Tokens[0]);
int32 rhs = FCString::Atoi(*Tokens[1]);
return lhs != rhs;
}
UE_LOG(LogTemp, Error, TEXT("Valid operator not found in subexpression!"));
return false;
}
bool
UAbilityDataFunctionLibrary::ParseConditionalExpressionToBool(const FString& Expression)
{
// Empty conditionals resolve to true
if (Expression.IsEmpty()) {
return true;
}
TArray<TCHAR> operators; // store (, ), |, and &
TArray<bool> operands; // store bools after converting an expression
FString currentSubExpression; // store current sub expression (numbers>numbers etc) (due to collapsing these immediately, only one will need to be processed at a time)
TArray<TCHAR> operatorChars = { '(', ')', '|', '&' };
TArray<TCHAR> subExpressionChars = { '>', '<', '=', '!' }; // or any digit
// shunting yard algo:
// keep going til you drop DOWN in precedence, at that point collapse
// -a ) will cause collapsing til you get to (.
// & and | are operands
// <, >, =, != aren't considered operators per se, they collapse to bools using evaluateExpression
// end of input will cause collapsing
int32 currentIndex = 0;
while (currentIndex < Expression.Len()) {
TCHAR ch = Expression[currentIndex];
// Start collapse of ACTUAL expression
if (ch == ')') {
if (!operators.IsEmpty()) {
while (!operators.IsEmpty() && operators.Top() != '(' && operands.Num() > 1) {
bool B = operands.Pop();
bool A = operands.Pop();
TCHAR op = operators.Pop();
bool result;
if (op == '&') {
result = A && B;
UE_LOG(LogTemp, Error, TEXT("Calling &"));
} else if (op == '|') {
result = A || B;
UE_LOG(LogTemp, Error, TEXT("Calling |"));
} else {
UE_LOG(LogTemp, Error, TEXT("Invalid operator found!"));
return false;
}
operands.Push(result);
}
} else {
UE_LOG(LogTemp, Error, TEXT("Invalid expression, found ) with nothing to collapse!"));
return false;
}
}
// Once we find a digit, need to collapse subexpression to a bool.
else if (FChar::IsDigit(ch)) {
// Build the subexpression
while (currentIndex < Expression.Len() && (FChar::IsDigit(Expression[currentIndex]) || subExpressionChars.Contains(Expression[currentIndex]))) {
currentSubExpression.AppendChar(Expression[currentIndex++]);
}
// Convert the sub expression to a bool, and put it on the operands stack
operands.Push(EvaluateSubExpression(currentSubExpression));
currentSubExpression = "";
currentIndex--; // Move the index backwards so that the increment below doesn't move the pointer forward.
}
// Any other operators get put onto the stack without triggering anything
else if (operatorChars.Contains(ch)) {
operators.Push(ch);
} else {
UE_LOG(LogTemp, Error, TEXT("Invalid character!"));
return false;
}
currentIndex++;
}
// Evaluate any remaining expressions in the stacks
while (!operators.IsEmpty() && operators.Top() != '(' && operands.Num() > 1) {
bool B = operands.Pop();
bool A = operands.Pop();
TCHAR op = operators.Pop();
bool result;
if (op == '&') {
result = A && B;
} else if (op == '|') {
result = A || B;
} else {
UE_LOG(LogTemp, Error, TEXT("Invalid operator found!"));
return false;
}
operands.Push(result);
}
if (operands.Num() != 1) {
UE_LOG(LogTemp, Error, TEXT("Remaining operands after collapse != 1!"));
}
// The final result will be on the top of the operands stack
return operands.Top();
}
int
UAbilityDataFunctionLibrary::ParseExpressionToInteger(const FString& Expression)
{
// Empty conditionals resolve to 0
if (Expression.IsEmpty()) {
return true;
}
TArray<TCHAR> operators; // store +, -, and *
TArray<int> operands; // store int after converting an expression
FString currentInteger; // store current sub expression (numbers>numbers etc) (due to collapsing these immediately, only one will need to be processed at a time)
TArray<TCHAR> operatorChars = { '(', ')', '+', '-', '*', '/' };
// shunting yard algo:
// keep going til you drop DOWN in precedence, at that point collapse
// -a ) will cause collapsing til you get to (.
// & and | are operands
// <, >, =, != aren't considered operators per se, they collapse to bools using evaluateExpression
// end of input will cause collapsing
int32 currentIndex = 0;
while (currentIndex < Expression.Len()) {
TCHAR ch = Expression[currentIndex];
// Start collapse of ACTUAL expression
if (ch == ')') {
if (!operators.IsEmpty()) {
while (!operators.IsEmpty() && operators.Top() != '(' && operands.Num() > 1) {
int B = operands.Pop();
int A = operands.Pop();
TCHAR op = operators.Pop();
int result;
if (op == '+') {
result = A + B;
} else if (op == '-') {
result = A - B;
} else if (op == '*') {
result = A * B;
} else if (op == '/') {
result = A / B;
} else {
UE_LOG(LogTemp, Error, TEXT("Invalid operator found!"));
return false;
}
operands.Push(result);
}
} else {
UE_LOG(LogTemp, Error, TEXT("Invalid expression, found ) with nothing to collapse!"));
return false;
}
}
// Once we find a digit, need to parse full length to an int.
else if (FChar::IsDigit(ch)) {
// Build the subexpression
while (currentIndex < Expression.Len() && FChar::IsDigit(Expression[currentIndex])) {
currentInteger.AppendChar(Expression[currentIndex++]);
}
// Convert the sub expression to a bool, and put it on the operands stack
operands.Push(FCString::Atoi(*currentInteger));
currentInteger = "";
currentIndex--; // Move the index backwards so that the increment below doesn't move the pointer forward.
}
// Any other operators get put onto the stack without triggering anything
else if (operatorChars.Contains(ch)) {
operators.Push(ch);
} else {
UE_LOG(LogTemp, Error, TEXT("Invalid character!"));
return false;
}
currentIndex++;
}
// Evaluate any remaining expressions in the stacks
while (!operators.IsEmpty() && operators.Top() != '(' && operands.Num() > 1) {
int B = operands.Pop();
int A = operands.Pop();
TCHAR op = operators.Pop();
int result;
if (op == '+') {
result = A + B;
} else if (op == '-') {
result = A - B;
} else if (op == '*') {
result = A * B;
} else {
UE_LOG(LogTemp, Error, TEXT("Invalid operator found!"));
return false;
}
operands.Push(result);
}
if (operands.Num() != 1) {
UE_LOG(LogTemp, Error, TEXT("Remaining operands after collapse != 1!"));
}
// The final result will be on the top of the operands stack
return operands.Top();
}
FString
UAbilityDataFunctionLibrary::SolveTerm(const FString& InputTerm, FAttributeDieFace AttributeData, AActor* Caller = nullptr)
{
FString coefficient;
FString attribute;
if (InputTerm.Split(TEXT("x"), &coefficient, &attribute)) {
// Parse attribute chars
int32 valueFromData = 0;
if (attribute == TEXT("S")) {
valueFromData = AttributeData.Strength;
} else if (attribute == TEXT("D")) {
valueFromData = AttributeData.Dexterity;
} else if (attribute == TEXT("I")) {
valueFromData = AttributeData.Intelligence;
} else if (attribute == TEXT("F")) {
valueFromData = AttributeData.Failure;
} else if (attribute == TEXT("CF")) {
valueFromData = AttributeData.CriticalFailure;
} else {
bool success = false;
// Other terms need data beyond the die roll sum, check for it in the player state.
if (Caller) {
if (auto callingChar = Cast<ASecretDungeonPlayerController>(Caller)) {
if (auto ps = callingChar->GetPlayerState<ASecretDungeonPlayerState>()) {
// Terms with letter and number e.g. D1 mean to check the first die's value (instead of the sum)
if (attribute.Len() == 2 && attribute.Right(1).IsNumeric()) {
int dieIndex = FCString::Atoi(*attribute.Right(1)) - 1;
if (ps->GetLastAttributeDiceRollResults().IsValidIndex(dieIndex)) {
auto selectedDieResult = ps->GetLastAttributeDiceRollResults()[dieIndex];
FString attributeChop = attribute.Left(1);
if (attributeChop == TEXT("S")) {
valueFromData = selectedDieResult.Strength;
success = true;
} else if (attributeChop == TEXT("D")) {
valueFromData = selectedDieResult.Dexterity;
success = true;
} else if (attributeChop == TEXT("I")) {
valueFromData = selectedDieResult.Intelligence;
success = true;
} else if (attributeChop == TEXT("F")) {
valueFromData = selectedDieResult.Failure;
success = true;
}
UE_LOG(LogTemp, Warning, TEXT("Chosen specific die index %i, got value %d"), dieIndex, valueFromData);
} else {
UE_LOG(LogTemp, Warning, TEXT("Last die rolls dont contain index %i, num is %i"), dieIndex, ps->GetLastAttributeDiceRollResults().Num());
}
}
// Combat Data is a dict on the player state with other misc attributes.
float value;
if (ps->GetCombatData(attribute, value)) {
valueFromData = FMath::RoundToInt(value);
success = true;
}
}
}
}
if (!success) {
UE_LOG(LogTemp, Error, TEXT("Unable to parse attribute in term: %s"), *attribute);
valueFromData = 0;
}
}
int32 coefficientValue = FCString::Atoi(*coefficient);
if (coefficient.IsEmpty()) {
coefficientValue = 1;
}
return FString::Printf(TEXT("%d"), valueFromData * coefficientValue);
}
UE_LOG(LogTemp, Error, TEXT("No x found in term!"));
return FString("0");
}
FString
UAbilityDataFunctionLibrary::SolveAllTerms(const FString& InputTerm, FAttributeDieFace AttributeData, AActor* Caller = nullptr)
{
FString processedString = FString(InputTerm);
FString processedStringLeft;
FString processedStringCenterAndRight;
FString processedStringCenter;
FString processedStringRight;
while (processedString.Split(TEXT("["), &processedStringLeft, &processedStringCenterAndRight)) {
if (processedStringCenterAndRight.Split(TEXT("]"), &processedStringCenter, &processedStringRight)) {
processedString = processedStringLeft + SolveTerm(processedStringCenter, AttributeData, Caller) + processedStringRight;
} else {
UE_LOG(LogTemp, Error, TEXT("Expected matching ]!"));
}
}
return processedString;
}
void
UAbilityDataFunctionLibrary::RemoveReplacedEffects(const TArray<FPlayerAbilityData_DieRoll_Effect>& EffectsList, FAttributeDieFace DieRollResult, TArray<FPlayerAbilityData_DieRoll_Effect>& FilteredEffectsList, AActor* Caller = nullptr)
{
FilteredEffectsList = {};
TArray<int> indecesToRemove;
for (int i = 0; i < EffectsList.Num(); i++) {
auto effect = EffectsList[i];
bool currentEffectActive = ParseConditionalExpressionToBool(SolveAllTerms(effect.ConditionExpression, DieRollResult, Caller));
if (currentEffectActive) {
for (auto indexToRemove : effect.EffectsToReplaceIfTrue) {
indecesToRemove.Add(indexToRemove);
}
}
}
for (int i = 0; i < EffectsList.Num(); i++) {
if (!indecesToRemove.Contains(i)) {
FilteredEffectsList.Add(EffectsList[i]);
}
}
}
void
UAbilityDataFunctionLibrary::GetActualTargetsOfGameplayEffect(
ASecretDungeonPlayerController* Caster,
FPlayerAbilityData_DieRoll_Effect Effect,
EPlayerAbilityTargetingType AbilityTargetingType,
TEnumAsByte<ETargetDataFilterSelf::Type> IncludeSelfOrNot,
FGameplayAbilityTargetDataHandle TargetDataHandle,
TArray<ASecretDungeonPlayerController*> Allies,
TArray<ASecretDungeonAIEnemyBase*> Enemies,
TArray<AActor*>& FinalTargets)
{
ASecretDungeonPlayerState* casterPlayerState = Caster->GetPlayerState<ASecretDungeonPlayerState>();
TArray<ASecretDungeonCharacterBase*> alliedPawns;
Algo::Transform(Allies, alliedPawns, [](ASecretDungeonPlayerController* pc) {
return pc->GetPawn<ASecretDungeonCharacterBase>();
});
// Only living + visible pawns are relevant.
alliedPawns = alliedPawns.FilterByPredicate([](ASecretDungeonCharacterBase* character) {
bool isAlive = !character->GetPlayerState<ASecretDungeonPlayerState>()->GetAbilitySystemComponent()->HasMatchingGameplayTag(UAbilityDataFunctionLibrary::DeadTag);
return isAlive && !character->IsHidden();
});
// Pawns whose hostility setting doesn't match are targetable as "enemies".
TArray<ASecretDungeonCharacterBase*> hostileAlliedPawns = alliedPawns.FilterByPredicate([casterPlayerState](ASecretDungeonCharacterBase* character) {
bool matchesHostility = casterPlayerState->bIsHostile == character->GetPlayerState<ASecretDungeonPlayerState>()->bIsHostile;
return !matchesHostility;
});
// Use the opposite filter to make sure we only consider allies who match hostility setting as "allies"
alliedPawns = alliedPawns.FilterByPredicate([casterPlayerState](ASecretDungeonCharacterBase* character) {
bool matchesHostility = casterPlayerState->bIsHostile == character->GetPlayerState<ASecretDungeonPlayerState>()->bIsHostile;
return matchesHostility;
});
// Only visible + living Enemies are relevant
Enemies = Enemies.FilterByPredicate([](ASecretDungeonAIEnemyBase* enemy) {
bool isAlive = !enemy->GetAbilitySystemComponent()->HasMatchingGameplayTag(UAbilityDataFunctionLibrary::DeadTag);
return isAlive && !enemy->IsHidden();
});
// Override the target because of Blinded Debuff.
if (casterPlayerState->GetAbilitySystemComponent()->HasMatchingGameplayTag(UAbilityDataFunctionLibrary::BlindedTag)) {
float RandomSeed = FMath::RandRange(0.f, 1.f);
if (RandomSeed <= 0.5f) {
Effect.EffectTargetingType = EPlayerAbilityEffectTargetingType::RandomAlly;
}
}
// Select the targets
TArray<TWeakObjectPtr<AActor>> ActualTargets;
for (auto ti = 0; ti < TargetDataHandle.Num(); ti++) {
ActualTargets.Append(TargetDataHandle.Get(ti)->GetActors());
}
/* Moved this to happen earlier in BP so we can spawn false targeters.
// Randomize targeters if the caster is confused
if (casterPlayerState->GetAbilitySystemComponent()->HasMatchingGameplayTag(UAbilityDataFunctionLibrary::ConfusedTag)) {
if (Effect.EffectTargetingType == EPlayerAbilityEffectTargetingType::ChosenTargets) {
// change the chosen targets to random playres, enemies, etc.
switch (AbilityTargetingType) {
case EPlayerAbilityTargetingType::SingleEnemy: {
Effect.EffectTargetingType = EPlayerAbilityEffectTargetingType::RandomEnemy;
break;
}
case EPlayerAbilityTargetingType::SingleAlly: {
if (IncludeSelfOrNot == ETargetDataFilterSelf::TDFS_NoSelf) {
Effect.EffectTargetingType = EPlayerAbilityEffectTargetingType::RandomAlly;
} else if (IncludeSelfOrNot == ETargetDataFilterSelf::TDFS_Any) {
Effect.EffectTargetingType = EPlayerAbilityEffectTargetingType::RandomAllyIncludingSelf;
}
break;
}
case EPlayerAbilityTargetingType::AnyNumberOfEnemies: {
Effect.EffectTargetingType = EPlayerAbilityEffectTargetingType::RandomEnemy;
break;
}
case EPlayerAbilityTargetingType::AnyNumberOfEnemiesRollPer: {
Effect.EffectTargetingType = EPlayerAbilityEffectTargetingType::RandomEnemy;
break;
}
}
}
}*/
switch (Effect.EffectTargetingType) {
case EPlayerAbilityEffectTargetingType::ChosenTargets: {
break;
}
case EPlayerAbilityEffectTargetingType::Self: {
ActualTargets = { Caster->GetPawn() };
break;
}
case EPlayerAbilityEffectTargetingType::RandomAll: {
auto targetIndex = FMath::RandRange(0, Enemies.Num() + alliedPawns.Num() - 1);
AActor* targetActor = targetIndex >= Enemies.Num() ? (AActor*)alliedPawns[targetIndex - Enemies.Num()] : (AActor*)Enemies[targetIndex];
ActualTargets = { targetActor };
break;
}
case EPlayerAbilityEffectTargetingType::RandomAllyIncludingSelf: {
ActualTargets.Empty();
if (casterPlayerState->bIsHostile) {
ActualTargets.Append(Enemies);
}
ActualTargets.Append(alliedPawns);
ActualTargets.AddUnique(Caster->GetPawn<ASecretDungeonCharacterBase>());
// Choose random or empty
if (!ActualTargets.IsEmpty()) {
ActualTargets = { ActualTargets[FMath::RandRange(0, ActualTargets.Num() - 1)] };
}
break;
}
case EPlayerAbilityEffectTargetingType::RandomAlly: {
ActualTargets = {};
// exclude self from the list to randomly choose from
alliedPawns.Remove(Cast<ASecretDungeonCharacterBase>(Caster->GetPawn()));
if (!alliedPawns.IsEmpty()) {
ActualTargets = { alliedPawns[FMath::RandRange(0, alliedPawns.Num() - 1)] };
}
// Hostile casters consider enemies as allies
if (casterPlayerState->bIsHostile) {
ActualTargets = {};
if (!Enemies.IsEmpty()) {
ActualTargets = { Enemies[FMath::RandRange(0, Enemies.Num() - 1)] };
}
}
break;
}
case EPlayerAbilityEffectTargetingType::RandomAllyOrEnemy: {
ActualTargets.Empty();
ActualTargets.Append(Enemies);
ActualTargets.Append(alliedPawns);
ActualTargets.Append(hostileAlliedPawns);
ActualTargets.Remove(Caster->GetPawn<ASecretDungeonCharacterBase>());
// Choose random or empty
if (!ActualTargets.IsEmpty()) {
ActualTargets = { ActualTargets[FMath::RandRange(0, ActualTargets.Num() - 1)] };
}
break;
}
case EPlayerAbilityEffectTargetingType::RandomEnemy: {
ActualTargets.Empty();
if (!casterPlayerState->bIsHostile) {
ActualTargets.Append(Enemies);
}
ActualTargets.Append(hostileAlliedPawns);
ActualTargets.Remove(Caster->GetPawn<ASecretDungeonCharacterBase>());
// Choose random or empty
if (!ActualTargets.IsEmpty()) {
ActualTargets = { ActualTargets[FMath::RandRange(0, ActualTargets.Num() - 1)] };
}
break;
}
case EPlayerAbilityEffectTargetingType::AllAllies: {
ActualTargets.Empty();
for (auto ally : alliedPawns) {
ActualTargets.Add((AActor*)ally);
}
// Hostile casters will include enemies
if (casterPlayerState->bIsHostile) {
for (auto enemy : Enemies) {
ActualTargets.Add((AActor*)enemy);
}
}
break;
}
case EPlayerAbilityEffectTargetingType::AllEnemies: {
ActualTargets.Empty();
if (!casterPlayerState->bIsHostile) {
for (auto enemy : Enemies) {
ActualTargets.Add((AActor*)enemy);
}
}
// Include hostile allies
for (auto hostileAlly : hostileAlliedPawns) {
if (hostileAlly != Caster->GetPawn<ASecretDungeonCharacterBase>()) {
ActualTargets.Add(hostileAlly);
}
}
break;
}
case EPlayerAbilityEffectTargetingType::LowestHealthAlly: {
TArray<AActor*> lowestHPTargets = {};
float lowestHealth = 9999.9f;
for (auto ally : alliedPawns) {
float allyHealth = ally->GetPlayerState<ASecretDungeonPlayerState>()->GetBaseAttributeSet()->GetHealth();
if (allyHealth > 0 && allyHealth < lowestHealth) {
lowestHealth = allyHealth;
lowestHPTargets = { (AActor*)ally };
} else if (allyHealth > 0 && allyHealth == lowestHealth) {
lowestHPTargets.Add((AActor*)ally);
}
}
// Hostile casters also consider enemies to be "allies"
if (casterPlayerState->bIsHostile) {
for (auto enemy : Enemies) {
float enemyHealth = enemy->GetBaseAttributeSet()->GetHealth();
if (enemyHealth > 0 && enemyHealth < lowestHealth) {
lowestHealth = enemyHealth;
lowestHPTargets = { (AActor*)enemy };
} else if (enemyHealth > 0 && enemyHealth == lowestHealth) {
lowestHPTargets.Add((AActor*)enemy);
}
}
}
ActualTargets.Empty();
if (!lowestHPTargets.IsEmpty()) {
ActualTargets = { lowestHPTargets[FMath::RandRange(0, lowestHPTargets.Num() - 1)] };
}
}
}
// Copy ActualTargets into FinalTargets
FinalTargets.Empty();
for (auto a : ActualTargets) {
FinalTargets.Add(a.Get());
}
}
void
UAbilityDataFunctionLibrary::CheckIfAbilityGameplayEffectWillTrigger(
ASecretDungeonPlayerController* Caster,
FPlayerAbilityData_DieRoll_Effect Effect,
FAttributeDieFace DieRollResult,
bool& EffectWillTrigger)
{
// Cancel execution if the condition isn't met.
EffectWillTrigger = ParseConditionalExpressionToBool(SolveAllTerms(Effect.ConditionExpression, DieRollResult, Caster));
}
void
UAbilityDataFunctionLibrary::ApplyAbilityGameplayEffect(
ASecretDungeonPlayerController* Caster,
UGameplayAbility* Ability,
FPlayerAbilityData_DieRoll_Effect Effect,
const TArray<AActor*>& ActualTargets,
FAttributeDieFace DieRollResult)
{
ASecretDungeonPlayerState* casterPlayerState = Caster->GetPlayerState<ASecretDungeonPlayerState>();
// Cancel execution if the condition isn't met.
bool calculatedConditional = ParseConditionalExpressionToBool(SolveAllTerms(Effect.ConditionExpression, DieRollResult, Caster));
if (!calculatedConditional) {
return;
}
int calculatedMagnitude = ParseExpressionToInteger(SolveAllTerms(Effect.MagnitudeExpression, DieRollResult, Caster));
calculatedMagnitude = FMath::Max(calculatedMagnitude, 0);
// do not do anything if the maginitude rolled for the effect is 0;
if (calculatedMagnitude == 0 && (Effect.Type != EPlayerAbilityEffectType::Baited || Effect.Type != EPlayerAbilityEffectType::Baiting)) {
return;
}
for (auto TargetActor : ActualTargets) {
switch (Effect.Type) {
case EPlayerAbilityEffectType::Miss: // Does nothing
break;
case EPlayerAbilityEffectType::DamageX: // Lower target HP by X
{
auto effectSpec = Ability->MakeOutgoingGameplayEffectSpec(this->DamageEffect);
effectSpec.Data->SetSetByCallerMagnitude(UAbilityDataFunctionLibrary::MagnitudeDataTag, calculatedMagnitude);
// Test for Dazzled Debuff
if (casterPlayerState->GetAbilitySystemComponent()->HasMatchingGameplayTag(UAbilityDataFunctionLibrary::DazzledTag)) {
float RandomSeed = FMath::RandRange(0.f, 1.f);
if (RandomSeed <= 0.5f) {
return;
}
}
if (ASecretDungeonCharacterBase* TargetPlayer = Cast<ASecretDungeonCharacterBase>(TargetActor)) {
TargetPlayer->GetPlayerState<ASecretDungeonPlayerState>()->GetAbilitySystemComponent()->ApplyGameplayEffectSpecToSelf(*effectSpec.Data.Get());
} else if (ASecretDungeonAIEnemyBase* TargetEnemy = Cast<ASecretDungeonAIEnemyBase>(TargetActor)) {
TargetEnemy->GetAbilitySystemComponent()->ApplyGameplayEffectSpecToSelf(*effectSpec.Data.Get());
} else {
UE_LOG(LogTemp, Error, TEXT("Invalid target for damage!"));
}
break;
}
case EPlayerAbilityEffectType::HealX: // Raise target HP by X
{
auto effectSpec = Ability->MakeOutgoingGameplayEffectSpec(this->HealEffect);
effectSpec.Data->SetSetByCallerMagnitude(UAbilityDataFunctionLibrary::MagnitudeDataTag, calculatedMagnitude);
if (ASecretDungeonCharacterBase* TargetPlayer = Cast<ASecretDungeonCharacterBase>(TargetActor)) {
TargetPlayer->GetPlayerState<ASecretDungeonPlayerState>()->GetAbilitySystemComponent()->ApplyGameplayEffectSpecToSelf(*effectSpec.Data.Get());
} else if (ASecretDungeonAIEnemyBase* TargetEnemy = Cast<ASecretDungeonAIEnemyBase>(TargetActor)) {
TargetEnemy->GetAbilitySystemComponent()->ApplyGameplayEffectSpecToSelf(*effectSpec.Data.Get());
} else {
UE_LOG(LogTemp, Error, TEXT("Invalid target for heal!"));
}
break;
}
case EPlayerAbilityEffectType::ShieldX: // Remove X damage from any Damage (Same as BlockX?)
{
auto effectSpec = Ability->MakeOutgoingGameplayEffectSpec(this->ShieldEffect);
effectSpec.Data->SetSetByCallerMagnitude(UAbilityDataFunctionLibrary::MagnitudeDataTag, calculatedMagnitude);
if (ASecretDungeonCharacterBase* TargetPlayer = Cast<ASecretDungeonCharacterBase>(TargetActor)) {
TargetPlayer->GetPlayerState<ASecretDungeonPlayerState>()->GetAbilitySystemComponent()->ApplyGameplayEffectSpecToSelf(*effectSpec.Data.Get());
} else if (ASecretDungeonAIEnemyBase* TargetEnemy = Cast<ASecretDungeonAIEnemyBase>(TargetActor)) {
TargetEnemy->GetAbilitySystemComponent()->ApplyGameplayEffectSpecToSelf(*effectSpec.Data.Get());
} else {
UE_LOG(LogTemp, Error, TEXT("Invalid target for shield!"));
}
break;
}
case EPlayerAbilityEffectType::WoundX: // Add X damage to any Damage
{
auto effectSpec = Ability->MakeOutgoingGameplayEffectSpec(this->WoundEffect);
effectSpec.Data->SetSetByCallerMagnitude(UAbilityDataFunctionLibrary::MagnitudeDataTag, calculatedMagnitude);
if (ASecretDungeonCharacterBase* TargetPlayer = Cast<ASecretDungeonCharacterBase>(TargetActor)) {
TargetPlayer->GetPlayerState<ASecretDungeonPlayerState>()->GetAbilitySystemComponent()->ApplyGameplayEffectSpecToSelf(*effectSpec.Data.Get());
} else if (ASecretDungeonAIEnemyBase* TargetEnemy = Cast<ASecretDungeonAIEnemyBase>(TargetActor)) {
TargetEnemy->GetAbilitySystemComponent()->ApplyGameplayEffectSpecToSelf(*effectSpec.Data.Get());
} else {
UE_LOG(LogTemp, Error, TEXT("Invalid target for wound!"));
}
break;
}
case EPlayerAbilityEffectType::Protecting: // Taking all damage for an ally (Pairs with Protected) -- Associated state data = protected player
{
auto effectSpecProtecting = Ability->MakeOutgoingGameplayEffectSpec(this->ProtectEffect);
auto effectSpecProtected = Ability->MakeOutgoingGameplayEffectSpec(this->ProtectedEffect);
if (ASecretDungeonCharacterBase* ProtectedPlayer = Cast<ASecretDungeonCharacterBase>(TargetActor)) {
casterPlayerState->GetAbilitySystemComponent()->ApplyGameplayEffectSpecToSelf(*effectSpecProtecting.Data.Get());
casterPlayerState->ProtectedPlayers.Empty();
casterPlayerState->ProtectedPlayerNames.Empty();
casterPlayerState->ProtectedPlayers.AddUnique(ProtectedPlayer->OwningPlayerPtr);
casterPlayerState->ProtectedPlayerNames.AddUnique(ProtectedPlayer->OwningPlayerPtr->ChosenPlayerName);
ProtectedPlayer->GetPlayerState<ASecretDungeonPlayerState>()->GetAbilitySystemComponent()->ApplyGameplayEffectSpecToSelf(*effectSpecProtected.Data.Get());
ProtectedPlayer->GetPlayerState<ASecretDungeonPlayerState>()->ProtectingPlayer = Caster;
ProtectedPlayer->GetPlayerState<ASecretDungeonPlayerState>()->ProtectingPlayerName = Caster->ChosenPlayerName;
} else {
UE_LOG(LogTemp, Error, TEXT("Invalid target for protected player!"));
}
break;
}
case EPlayerAbilityEffectType::Protected: // Redirecting all damage to an ally (Pairs with Protecting) -- Associated state data = protector player
{
auto effectSpecProtecting = Ability->MakeOutgoingGameplayEffectSpec(this->ProtectEffect);
auto effectSpecProtected = Ability->MakeOutgoingGameplayEffectSpec(this->ProtectedEffect);
if (ASecretDungeonCharacterBase* ProtectedPlayer = Cast<ASecretDungeonCharacterBase>(TargetActor)) {
casterPlayerState->GetAbilitySystemComponent()->ApplyGameplayEffectSpecToSelf(*effectSpecProtected.Data.Get());
casterPlayerState->ProtectingPlayer = ProtectedPlayer->OwningPlayerPtr;
casterPlayerState->ProtectingPlayerName = ProtectedPlayer->OwningPlayerPtr->ChosenPlayerName;
ProtectedPlayer->GetPlayerState<ASecretDungeonPlayerState>()->GetAbilitySystemComponent()->ApplyGameplayEffectSpecToSelf(*effectSpecProtecting.Data.Get());
ProtectedPlayer->GetPlayerState<ASecretDungeonPlayerState>()->ProtectedPlayers.Empty();
ProtectedPlayer->GetPlayerState<ASecretDungeonPlayerState>()->ProtectedPlayerNames.Empty();
ProtectedPlayer->GetPlayerState<ASecretDungeonPlayerState>()->ProtectedPlayers.AddUnique(ProtectedPlayer->OwningPlayerPtr);
ProtectedPlayer->GetPlayerState<ASecretDungeonPlayerState>()->ProtectedPlayerNames.AddUnique(ProtectedPlayer->OwningPlayerPtr->ChosenPlayerName);
} else {
UE_LOG(LogTemp, Error, TEXT("Invalid target for protected player!"));
}
break;
}
case EPlayerAbilityEffectType::CounterX: // Deal X damage to any enemy that damages them
{
auto effectSpec = Ability->MakeOutgoingGameplayEffectSpec(this->CounterEffect);
effectSpec.Data->SetSetByCallerMagnitude(UAbilityDataFunctionLibrary::MagnitudeDataTag, calculatedMagnitude);
if (ASecretDungeonCharacterBase* TargetPlayer = Cast<ASecretDungeonCharacterBase>(TargetActor)) {
TargetPlayer->GetPlayerState<ASecretDungeonPlayerState>()->GetAbilitySystemComponent()->ApplyGameplayEffectSpecToSelf(*effectSpec.Data.Get());
} else if (ASecretDungeonAIEnemyBase* TargetEnemy = Cast<ASecretDungeonAIEnemyBase>(TargetActor)) {
TargetEnemy->GetAbilitySystemComponent()->ApplyGameplayEffectSpecToSelf(*effectSpec.Data.Get());
} else {
UE_LOG(LogTemp, Error, TEXT("Invalid target for counter!"));
}
break;
}
case EPlayerAbilityEffectType::Vulnerable: // Double incoming damage
{
auto effectSpec = Ability->MakeOutgoingGameplayEffectSpec(this->VulnerableEffect);
effectSpec.Data->SetSetByCallerMagnitude(UAbilityDataFunctionLibrary::MagnitudeDataTag, calculatedMagnitude);
if (ASecretDungeonCharacterBase* TargetPlayer = Cast<ASecretDungeonCharacterBase>(TargetActor)) {
TargetPlayer->GetPlayerState<ASecretDungeonPlayerState>()->GetAbilitySystemComponent()->ApplyGameplayEffectSpecToSelf(*effectSpec.Data.Get());
} else if (ASecretDungeonAIEnemyBase* TargetEnemy = Cast<ASecretDungeonAIEnemyBase>(TargetActor)) {
TargetEnemy->GetAbilitySystemComponent()->ApplyGameplayEffectSpecToSelf(*effectSpec.Data.Get());
} else {
UE_LOG(LogTemp, Error, TEXT("Invalid target for vulnerable!"));
}
break;
}
case EPlayerAbilityEffectType::Stun: // Skips their next turn
{
auto effectSpec = Ability->MakeOutgoingGameplayEffectSpec(this->StunEffect);
effectSpec.Data->SetSetByCallerMagnitude(UAbilityDataFunctionLibrary::MagnitudeDataTag, calculatedMagnitude);
if (ASecretDungeonCharacterBase* TargetPlayer = Cast<ASecretDungeonCharacterBase>(TargetActor)) {
TargetPlayer->GetPlayerState<ASecretDungeonPlayerState>()->GetAbilitySystemComponent()->ApplyGameplayEffectSpecToSelf(*effectSpec.Data.Get());
} else if (ASecretDungeonAIEnemyBase* TargetEnemy = Cast<ASecretDungeonAIEnemyBase>(TargetActor)) {
TargetEnemy->GetAbilitySystemComponent()->ApplyGameplayEffectSpecToSelf(*effectSpec.Data.Get());
} else {
UE_LOG(LogTemp, Error, TEXT("Invalid target for stun!"));
}
break;
}
case EPlayerAbilityEffectType::Infused: // Next attribute die roll gains extra amount to Fail: Crit Fail: and Main attribute -- Associated state data = infused stats
{
auto effectSpec = Ability->MakeOutgoingGameplayEffectSpec(this->InfuseEffect);
if (ASecretDungeonCharacterBase* InfusedPlayer = Cast<ASecretDungeonCharacterBase>(TargetActor)) {
InfusedPlayer->GetPlayerState<ASecretDungeonPlayerState>()->GetAbilitySystemComponent()->ApplyGameplayEffectSpecToSelf(*effectSpec.Data.Get());
FAttributeDieFace ConvertedDieRollResult = DieRollResult;
int SourceInt = DieRollResult.Intelligence;
switch (InfusedPlayer->GetPlayerState<ASecretDungeonPlayerState>()->ClassData.MainAttribute) {
case EPlayerAttributeType::STRENGTH:
ConvertedDieRollResult.Strength += SourceInt;
break;
case EPlayerAttributeType::INTELLIGENCE:
ConvertedDieRollResult.Intelligence += SourceInt;
break;
case EPlayerAttributeType::DEXTERITY:
ConvertedDieRollResult.Dexterity += SourceInt;
break;
}
ConvertedDieRollResult.Intelligence = 0;
InfusedPlayer->GetPlayerState<ASecretDungeonPlayerState>()->AddInfusedAttributeDieRollResult(ConvertedDieRollResult);
} else {
UE_LOG(LogTemp, Error, TEXT("Invalid target for infusing player!"));
}
break;
}
case EPlayerAbilityEffectType::Taunt: // Becomes the target for all single target attacksTargetEnemy
{
auto effectSpec = Ability->MakeOutgoingGameplayEffectSpec(this->TauntEffect);
if (ASecretDungeonCharacterBase* TargetPlayer = Cast<ASecretDungeonCharacterBase>(TargetActor)) {
TargetPlayer->GetPlayerState<ASecretDungeonPlayerState>()->GetAbilitySystemComponent()->ApplyGameplayEffectSpecToSelf(*effectSpec.Data.Get());
} else {
UE_LOG(LogTemp, Error, TEXT("Invalid target for taunt!"));
}
break;
}
case EPlayerAbilityEffectType::BlockX: // Remove X damage from any Damage (Same as ShieldX?)
{
auto effectSpec = Ability->MakeOutgoingGameplayEffectSpec(this->BlockEffect);
effectSpec.Data->SetSetByCallerMagnitude(UAbilityDataFunctionLibrary::MagnitudeDataTag, calculatedMagnitude);
if (ASecretDungeonCharacterBase* TargetPlayer = Cast<ASecretDungeonCharacterBase>(TargetActor)) {
TargetPlayer->GetPlayerState<ASecretDungeonPlayerState>()->GetAbilitySystemComponent()->ApplyGameplayEffectSpecToSelf(*effectSpec.Data.Get());
} else if (ASecretDungeonAIEnemyBase* TargetEnemy = Cast<ASecretDungeonAIEnemyBase>(TargetActor)) {
TargetEnemy->GetAbilitySystemComponent()->ApplyGameplayEffectSpecToSelf(*effectSpec.Data.Get());
} else {
UE_LOG(LogTemp, Error, TEXT("Invalid target for block!"));
}
break;
}
case EPlayerAbilityEffectType::CursedX: // Add X fail to any attribute die rolls
{
auto effectSpec = Ability->MakeOutgoingGameplayEffectSpec(this->CurseEffect);
effectSpec.Data->SetSetByCallerMagnitude(UAbilityDataFunctionLibrary::MagnitudeDataTag, calculatedMagnitude);
if (ASecretDungeonCharacterBase* TargetPlayer = Cast<ASecretDungeonCharacterBase>(TargetActor)) {
TargetPlayer->GetPlayerState<ASecretDungeonPlayerState>()->GetAbilitySystemComponent()->ApplyGameplayEffectSpecToSelf(*effectSpec.Data.Get());
} else if (ASecretDungeonAIEnemyBase* TargetEnemy = Cast<ASecretDungeonAIEnemyBase>(TargetActor)) {
TargetEnemy->GetAbilitySystemComponent()->ApplyGameplayEffectSpecToSelf(*effectSpec.Data.Get());
} else {
UE_LOG(LogTemp, Error, TEXT("Invalid target for curse!"));
}
break;
}
case EPlayerAbilityEffectType::BlessedX: // Add X to the main attribute dice roll result
{
auto effectSpec = Ability->MakeOutgoingGameplayEffectSpec(this->BlessEffect);
effectSpec.Data->SetSetByCallerMagnitude(UAbilityDataFunctionLibrary::MagnitudeDataTag, calculatedMagnitude);
if (ASecretDungeonCharacterBase* TargetPlayer = Cast<ASecretDungeonCharacterBase>(TargetActor)) {
TargetPlayer->GetPlayerState<ASecretDungeonPlayerState>()->GetAbilitySystemComponent()->ApplyGameplayEffectSpecToSelf(*effectSpec.Data.Get());
} else if (ASecretDungeonAIEnemyBase* TargetEnemy = Cast<ASecretDungeonAIEnemyBase>(TargetActor)) {
TargetEnemy->GetAbilitySystemComponent()->ApplyGameplayEffectSpecToSelf(*effectSpec.Data.Get());
} else {
UE_LOG(LogTemp, Error, TEXT("Invalid target for bless!"));
}
break;
}
case EPlayerAbilityEffectType::DamageUpX: // Add X damage to any Damage dealt
{
auto effectSpec = Ability->MakeOutgoingGameplayEffectSpec(this->DamageUpEffect);
effectSpec.Data->SetSetByCallerMagnitude(UAbilityDataFunctionLibrary::MagnitudeDataTag, calculatedMagnitude);
if (ASecretDungeonCharacterBase* TargetPlayer = Cast<ASecretDungeonCharacterBase>(TargetActor)) {
TargetPlayer->GetPlayerState<ASecretDungeonPlayerState>()->GetAbilitySystemComponent()->ApplyGameplayEffectSpecToSelf(*effectSpec.Data.Get());
} else if (ASecretDungeonAIEnemyBase* TargetEnemy = Cast<ASecretDungeonAIEnemyBase>(TargetActor)) {
TargetEnemy->GetAbilitySystemComponent()->ApplyGameplayEffectSpecToSelf(*effectSpec.Data.Get());
} else {
UE_LOG(LogTemp, Error, TEXT("Invalid target for DamageUp!"));
}
break;
}
case EPlayerAbilityEffectType::PoisonedX: // Take X damage at the end of the round
{
auto effectSpec = Ability->MakeOutgoingGameplayEffectSpec(this->PoisonEffect);
effectSpec.Data->SetSetByCallerMagnitude(UAbilityDataFunctionLibrary::MagnitudeDataTag, calculatedMagnitude);
if (ASecretDungeonCharacterBase* TargetPlayer = Cast<ASecretDungeonCharacterBase>(TargetActor)) {
TargetPlayer->GetPlayerState<ASecretDungeonPlayerState>()->GetAbilitySystemComponent()->ApplyGameplayEffectSpecToSelf(*effectSpec.Data.Get());
} else if (ASecretDungeonAIEnemyBase* TargetEnemy = Cast<ASecretDungeonAIEnemyBase>(TargetActor)) {
TargetEnemy->GetAbilitySystemComponent()->ApplyGameplayEffectSpecToSelf(*effectSpec.Data.Get());
} else {
UE_LOG(LogTemp, Error, TEXT("Invalid target for poison!"));
}
break;
}
case EPlayerAbilityEffectType::RegenerationX: // Heals X HP after the battle ends
{
auto effectSpec = Ability->MakeOutgoingGameplayEffectSpec(this->RegenerationEffect);
effectSpec.Data->SetSetByCallerMagnitude(UAbilityDataFunctionLibrary::MagnitudeDataTag, calculatedMagnitude);
if (ASecretDungeonCharacterBase* TargetPlayer = Cast<ASecretDungeonCharacterBase>(TargetActor)) {
TargetPlayer->GetPlayerState<ASecretDungeonPlayerState>()->GetAbilitySystemComponent()->ApplyGameplayEffectSpecToSelf(*effectSpec.Data.Get());
} else {
UE_LOG(LogTemp, Error, TEXT("Invalid target for regeration!"));
}
break;
}
case EPlayerAbilityEffectType::RewardX: // Gain X reward points after combat ends & before reward selection.
{
auto effectSpec = Ability->MakeOutgoingGameplayEffectSpec(this->RewardEffect);
effectSpec.Data->SetSetByCallerMagnitude(UAbilityDataFunctionLibrary::MagnitudeDataTag, calculatedMagnitude);
if (ASecretDungeonCharacterBase* TargetPlayer = Cast<ASecretDungeonCharacterBase>(TargetActor)) {
TargetPlayer->GetPlayerState<ASecretDungeonPlayerState>()->GetAbilitySystemComponent()->ApplyGameplayEffectSpecToSelf(*effectSpec.Data.Get());
} else {
UE_LOG(LogTemp, Error, TEXT("Invalid target for reward!"));
}
break;
}
case EPlayerAbilityEffectType::NewTurn: // Get a new turn immediately after current turn
{
auto effectSpec = Ability->MakeOutgoingGameplayEffectSpec(this->NewTurnEffect);
effectSpec.Data->SetSetByCallerMagnitude(UAbilityDataFunctionLibrary::MagnitudeDataTag, calculatedMagnitude);
if (ASecretDungeonCharacterBase* TargetPlayer = Cast<ASecretDungeonCharacterBase>(TargetActor)) {
TargetPlayer->GetPlayerState<ASecretDungeonPlayerState>()->GetAbilitySystemComponent()->ApplyGameplayEffectSpecToSelf(*effectSpec.Data.Get());
} else if (ASecretDungeonAIEnemyBase* TargetEnemy = Cast<ASecretDungeonAIEnemyBase>(TargetActor)) {
TargetEnemy->GetAbilitySystemComponent()->ApplyGameplayEffectSpecToSelf(*effectSpec.Data.Get());
} else {
UE_LOG(LogTemp, Error, TEXT("Invalid target for new turn!"));
}
break;
}
case EPlayerAbilityEffectType::Dazzled: // 50% chance to miss an attack
{
auto effectSpec = Ability->MakeOutgoingGameplayEffectSpec(this->DazzledEffect);
// effectSpec.Data->SetSetByCallerMagnitude(UAbilityDataFunctionLibrary::MagnitudeDataTag, calculatedMagnitude);
if (ASecretDungeonCharacterBase* TargetPlayer = Cast<ASecretDungeonCharacterBase>(TargetActor)) {
TargetPlayer->GetPlayerState<ASecretDungeonPlayerState>()->GetAbilitySystemComponent()->ApplyGameplayEffectSpecToSelf(*effectSpec.Data.Get());
} else if (ASecretDungeonAIEnemyBase* TargetEnemy = Cast<ASecretDungeonAIEnemyBase>(TargetActor)) {
TargetEnemy->GetAbilitySystemComponent()->ApplyGameplayEffectSpecToSelf(*effectSpec.Data.Get());
} else {
UE_LOG(LogTemp, Error, TEXT("Invalid target for Dazzled!"));
}
break;
}
case EPlayerAbilityEffectType::Blinded: // 50% chance to target a random ally instead
{
auto effectSpec = Ability->MakeOutgoingGameplayEffectSpec(this->BlindedEffect);
// effectSpec.Data->SetSetByCallerMagnitude(UAbilityDataFunctionLibrary::MagnitudeDataTag, calculatedMagnitude);
if (ASecretDungeonCharacterBase* TargetPlayer = Cast<ASecretDungeonCharacterBase>(TargetActor)) {
TargetPlayer->GetPlayerState<ASecretDungeonPlayerState>()->GetAbilitySystemComponent()->ApplyGameplayEffectSpecToSelf(*effectSpec.Data.Get());
} else if (ASecretDungeonAIEnemyBase* TargetEnemy = Cast<ASecretDungeonAIEnemyBase>(TargetActor)) {
TargetEnemy->GetAbilitySystemComponent()->ApplyGameplayEffectSpecToSelf(*effectSpec.Data.Get());
} else {
UE_LOG(LogTemp, Error, TEXT("Invalid target for Blinded!"));
}
break;
}
case EPlayerAbilityEffectType::Baiting: {
auto effectSpec = Ability->MakeOutgoingGameplayEffectSpec(this->BaitingEffect);
effectSpec.Data->SetSetByCallerMagnitude(UAbilityDataFunctionLibrary::MagnitudeDataTag, calculatedMagnitude);
if (ASecretDungeonCharacterBase* TargetPlayer = Cast<ASecretDungeonCharacterBase>(TargetActor)) {
TargetPlayer->GetPlayerState<ASecretDungeonPlayerState>()->GetAbilitySystemComponent()->ApplyGameplayEffectSpecToSelf(*effectSpec.Data.Get());
} else {
UE_LOG(LogTemp, Error, TEXT("Invalid target for Baiting!"));
}
break;
}
case EPlayerAbilityEffectType::Defiled: {
auto effectSpec = Ability->MakeOutgoingGameplayEffectSpec(this->DefileEffect);
// effectSpec.Data->SetSetByCallerMagnitude(UAbilityDataFunctionLibrary::MagnitudeDataTag, calculatedMagnitude);
if (ASecretDungeonCharacterBase* TargetPlayer = Cast<ASecretDungeonCharacterBase>(TargetActor)) {
TargetPlayer->GetPlayerState<ASecretDungeonPlayerState>()->GetAbilitySystemComponent()->ApplyGameplayEffectSpecToSelf(*effectSpec.Data.Get());
} else {
UE_LOG(LogTemp, Error, TEXT("Invalid target for Defiled!"));
}
break;
}
case EPlayerAbilityEffectType::Persecuting: {
auto effectSpec = Ability->MakeOutgoingGameplayEffectSpec(this->PersecutingEffect);
// effectSpec.Data->SetSetByCallerMagnitude(UAbilityDataFunctionLibrary::MagnitudeDataTag, calculatedMagnitude);
if (ASecretDungeonCharacterBase* TargetPlayer = Cast<ASecretDungeonCharacterBase>(TargetActor)) {
casterPlayerState->GetAbilitySystemComponent()->ApplyGameplayEffectSpecToSelf(*effectSpec.Data.Get());
Caster->PersecutedPlayer = TargetPlayer;
} else {
UE_LOG(LogTemp, Error, TEXT("Invalid target for Persecuting!"));
}
break;
}
case EPlayerAbilityEffectType::Confused: {
auto effectSpec = Ability->MakeOutgoingGameplayEffectSpec(this->ConfuseEffect);
// effectSpec.Data->SetSetByCallerMagnitude(UAbilityDataFunctionLibrary::MagnitudeDataTag, calculatedMagnitude);
if (ASecretDungeonCharacterBase* TargetPlayer = Cast<ASecretDungeonCharacterBase>(TargetActor)) {
TargetPlayer->GetPlayerState<ASecretDungeonPlayerState>()->GetAbilitySystemComponent()->ApplyGameplayEffectSpecToSelf(*effectSpec.Data.Get());
} else {
UE_LOG(LogTemp, Error, TEXT("Invalid target for Confused!"));
}
break;
}
}
}
}
FGameplayTargetDataFilterHandle
UAbilityDataFunctionLibrary::MakeExcludeActorsFilterHandle(FExcludeActorListTargetDataFilter Filter, AActor* FilterActor)
{
FGameplayTargetDataFilter* NewFilter = new FExcludeActorListTargetDataFilter(Filter);
NewFilter->InitializeFilterContext(FilterActor);
FGameplayTargetDataFilterHandle FilterHandle;
FilterHandle.Filter = TSharedPtr<FGameplayTargetDataFilter>(NewFilter);
return FilterHandle;
}
void
UAbilityDataFunctionLibrary::LoadStaticTags()
{
MagnitudeDataTag = FGameplayTag::RequestGameplayTag(FName("GE.Data.Magnitude"));
ProtectingTag = FGameplayTag::RequestGameplayTag(FName("GE.Buff.Protecting"));
ProtectedTag = FGameplayTag::RequestGameplayTag(FName("GE.Buff.Protected"));
VulnerableTag = FGameplayTag::RequestGameplayTag(FName("GE.Debuff.Vulnerable"));
WoundedTag = FGameplayTag::RequestGameplayTag(FName("GE.Debuff.Wounded"));
StunnedTag = FGameplayTag::RequestGameplayTag(FName("GE.Debuff.Stunned"));
InfusedTag = FGameplayTag::RequestGameplayTag(FName("GE.Buff.Infused"));
TauntTag = FGameplayTag::RequestGameplayTag(FName("GE.Buff.Taunt"));
NewTurnTag = FGameplayTag::RequestGameplayTag(FName("GE.Buff.NewTurn"));
DeadTag = FGameplayTag::RequestGameplayTag(FName("GE.Debuff.Dead"));
ShieldTag = FGameplayTag::RequestGameplayTag(FName("GE.Buff.Shield"));
BlockTag = FGameplayTag::RequestGameplayTag(FName("GE.Buff.Block"));
BlessedTag = FGameplayTag::RequestGameplayTag(FName("GE.Buff.Blessed"));
CursedTag = FGameplayTag::RequestGameplayTag(FName("GE.Debuff.Cursed"));
DamageUpTag = FGameplayTag::RequestGameplayTag(FName("GE.Buff.DamageUp"));
DazzledTag = FGameplayTag::RequestGameplayTag(FName("GE.Debuff.Dazzled"));
BlindedTag = FGameplayTag::RequestGameplayTag(FName("GE.Debuff.Blinded"));
BaitingTag = FGameplayTag::RequestGameplayTag(FName("GE.Buff.Baiting"));
BaitedTag = FGameplayTag::RequestGameplayTag(FName("GE.Buff.Baited"));
LastChanceTag = FGameplayTag::RequestGameplayTag(FName("GE.Debuff.LastChance"));
DefiledTag = FGameplayTag::RequestGameplayTag(FName("GE.Debuff.Defiled"));
PersecutingTag = FGameplayTag::RequestGameplayTag(FName("GE.Debuff.Persecuting"));
ConfusedTag = FGameplayTag::RequestGameplayTag(FName("GE.Debuff.Confused"));
}
// This work is the property of the Secret Dungeon Team. All rights reserved.
#pragma once
#include "CoreMinimal.h"
#include "Kismet/BlueprintFunctionLibrary.h"
#include "SecretDungeonDataTypes.h"
#include "AbilityDataFunctionLibrary.generated.h"
/**
*
*/
UCLASS(BlueprintType)
class SECRETDUNGEON_API UAbilityDataFunctionLibrary : public UBlueprintFunctionLibrary
{
GENERATED_BODY()
public:
UPROPERTY(BlueprintReadWrite, Meta = (ExposeOnSpawn = true))
TSubclassOf<UGameplayEffect> DamageEffect;
UPROPERTY(BlueprintReadWrite, Meta = (ExposeOnSpawn = true))
TSubclassOf<UGameplayEffect> HealEffect;
UPROPERTY(BlueprintReadWrite, Meta = (ExposeOnSpawn = true))
TSubclassOf<UGameplayEffect> ShieldEffect;
UPROPERTY(BlueprintReadWrite, Meta = (ExposeOnSpawn = true))
TSubclassOf<UGameplayEffect> WoundEffect;
UPROPERTY(BlueprintReadWrite, Meta = (ExposeOnSpawn = true))
TSubclassOf<UGameplayEffect> ProtectEffect;
UPROPERTY(BlueprintReadWrite, Meta = (ExposeOnSpawn = true))
TSubclassOf<UGameplayEffect> ProtectedEffect;
UPROPERTY(BlueprintReadWrite, Meta = (ExposeOnSpawn = true))
TSubclassOf<UGameplayEffect> CounterEffect;
UPROPERTY(BlueprintReadWrite, Meta = (ExposeOnSpawn = true))
TSubclassOf<UGameplayEffect> VulnerableEffect;
UPROPERTY(BlueprintReadWrite, Meta = (ExposeOnSpawn = true))
TSubclassOf<UGameplayEffect> StunEffect;
UPROPERTY(BlueprintReadWrite, Meta = (ExposeOnSpawn = true))
TSubclassOf<UGameplayEffect> InfuseEffect;
UPROPERTY(BlueprintReadWrite, Meta = (ExposeOnSpawn = true))
TSubclassOf<UGameplayEffect> CurseEffect;
UPROPERTY(BlueprintReadWrite, Meta = (ExposeOnSpawn = true))
TSubclassOf<UGameplayEffect> BlessEffect;
UPROPERTY(BlueprintReadWrite, Meta = (ExposeOnSpawn = true))
TSubclassOf<UGameplayEffect> BlockEffect;
UPROPERTY(BlueprintReadWrite, Meta = (ExposeOnSpawn = true))
TSubclassOf<UGameplayEffect> DamageUpEffect;
UPROPERTY(BlueprintReadWrite, Meta = (ExposeOnSpawn = true))
TSubclassOf<UGameplayEffect> PoisonEffect;
UPROPERTY(BlueprintReadWrite, Meta = (ExposeOnSpawn = true))
TSubclassOf<UGameplayEffect> TauntEffect;
UPROPERTY(BlueprintReadWrite, Meta = (ExposeOnSpawn = true))
TSubclassOf<UGameplayEffect> RegenerationEffect;
UPROPERTY(BlueprintReadWrite, Meta = (ExposeOnSpawn = true))
TSubclassOf<UGameplayEffect> RewardEffect;
UPROPERTY(BlueprintReadWrite, Meta = (ExposeOnSpawn = true))
TSubclassOf<UGameplayEffect> NewTurnEffect;
UPROPERTY(BlueprintReadWrite, Meta = (ExposeOnSpawn = true))
TSubclassOf<UGameplayEffect> DazzledEffect;
UPROPERTY(BlueprintReadWrite, Meta = (ExposeOnSpawn = true))
TSubclassOf<UGameplayEffect> BlindedEffect;
UPROPERTY(BlueprintReadWrite, Meta = (ExposeOnSpawn = true))
TSubclassOf<UGameplayEffect> BaitingEffect;
UPROPERTY(BlueprintReadWrite, Meta = (ExposeOnSpawn = true))
TSubclassOf<UGameplayEffect> BaitedEffect;
UPROPERTY(BlueprintReadWrite, Meta = (ExposeOnSpawn = true))
TSubclassOf<UGameplayEffect> DefileEffect;
UPROPERTY(BlueprintReadWrite, Meta = (ExposeOnSpawn = true))
TSubclassOf<UGameplayEffect> PersecutingEffect;
UPROPERTY(BlueprintReadWrite, Meta = (ExposeOnSpawn = true))
TSubclassOf<UGameplayEffect> ConfuseEffect;
// Evaluates numerical conditionals like "12>6" -> true
UFUNCTION(BlueprintCallable, BlueprintPure)
static bool EvaluateSubExpression(const FString& SubExpression);
// Parse complex numerical conditionals like "12<=6&3>2" -> false
UFUNCTION(BlueprintCallable, BlueprintPure)
static bool ParseConditionalExpressionToBool(const FString& Expression);
// Parse numerical expressions to ints. like "3-2" -> 1
UFUNCTION(BlueprintCallable, BlueprintPure)
static int ParseExpressionToInteger(const FString& Expression);
// replaces a 1xD type term with a numerical string
UFUNCTION(BlueprintCallable, BlueprintPure)
static FString SolveTerm(const FString& InputTerm, FAttributeDieFace AttributeData, AActor* Caller);
// replaces any [1xD] type terms found in a string with numbers.
UFUNCTION(BlueprintCallable, BlueprintPure)
static FString SolveAllTerms(const FString& InputTerm, FAttributeDieFace AttributeData, AActor* Caller);
// removes any active effects that replace another effect ("Instead" type effects").
UFUNCTION(BlueprintCallable, BlueprintPure)
static void RemoveReplacedEffects(const TArray<FPlayerAbilityData_DieRoll_Effect>& EffectsList, FAttributeDieFace DieRollResult, TArray<FPlayerAbilityData_DieRoll_Effect>& FilteredEffectsList, AActor* Caller);
UFUNCTION(BlueprintCallable)
void GetActualTargetsOfGameplayEffect(
ASecretDungeonPlayerController* Caster,
FPlayerAbilityData_DieRoll_Effect Effect,
EPlayerAbilityTargetingType AbilityTargetingType,
TEnumAsByte<ETargetDataFilterSelf::Type> IncludeSelfOrNot,
FGameplayAbilityTargetDataHandle TargetDataHandle,
TArray<ASecretDungeonPlayerController*> Allies,
TArray<ASecretDungeonAIEnemyBase*> Enemies,
TArray<AActor*>& FinalTargets);
UFUNCTION(BlueprintCallable)
void CheckIfAbilityGameplayEffectWillTrigger(
ASecretDungeonPlayerController* Caster,
FPlayerAbilityData_DieRoll_Effect Effect,
FAttributeDieFace DieRollResult,
bool& EffectWillTrigger);
UFUNCTION(BlueprintCallable)
void ApplyAbilityGameplayEffect(
ASecretDungeonPlayerController* Caster,
UGameplayAbility* Ability,
FPlayerAbilityData_DieRoll_Effect Effect,
const TArray<AActor*>& ActualTargets,
FAttributeDieFace DieRollResult);
UFUNCTION(BlueprintCallable, BlueprintPure)
static FGameplayTargetDataFilterHandle MakeExcludeActorsFilterHandle(FExcludeActorListTargetDataFilter Filter, AActor* FilterActor);
UFUNCTION(BlueprintCallable)
static void LoadStaticTags();
inline static FGameplayTag MagnitudeDataTag;
inline static FGameplayTag ProtectingTag;
inline static FGameplayTag ProtectedTag;
inline static FGameplayTag VulnerableTag;
inline static FGameplayTag WoundedTag;
inline static FGameplayTag StunnedTag;
inline static FGameplayTag InfusedTag;
inline static FGameplayTag TauntTag;
inline static FGameplayTag NewTurnTag;
inline static FGameplayTag DeadTag;
inline static FGameplayTag ShieldTag;
inline static FGameplayTag BlockTag;
inline static FGameplayTag BlessedTag;
inline static FGameplayTag CursedTag;
inline static FGameplayTag DamageUpTag;
inline static FGameplayTag DazzledTag;
inline static FGameplayTag BlindedTag;
inline static FGameplayTag BaitingTag;
inline static FGameplayTag BaitedTag;
inline static FGameplayTag LastChanceTag;
inline static FGameplayTag DefiledTag;
inline static FGameplayTag PersecutingTag;
inline static FGameplayTag ConfusedTag;
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment