Created
March 15, 2024 03:40
-
-
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 file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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 file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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