Trying to create a utility ai for my combat. I won’t say too much other than it’s causing seemingly random crashes, sometimes on play, sometimes when clicking a folder out of play, sometimes just having the editor open, sometimes just clicking on a character in the viewport. Everything compiles so I would have thought it’s a null pointer access violation, but then why would it crash outside of play?
I’m including two crash folders, one before I commented out most of the code, and one after
Any npc->Scorer is referencing a UScores object, as npc->Behavior is referencing a UBehaviors object
Please ask for any and all clarifications. Trying to remain vague in case you spot logic errors on my part or some such case
UCombat.cpp
void UCombat::PlayerCombat(ANPC* npc, APlayer_Char* player)
{
if (npc)
{
if (npc->Behavior && npc->Scorer)
{
npc->Behavior->Attack(npc);
} else
{
npc->Behavior = NewObject<UBehaviors>(npc);
npc->Scorer = NewObject<UScores>(npc);
//Setting the references of the npc's behavior scores a single time to the action behavior scores
npc->behaviorScores.attackAction = &npc->Behavior->attackAction;
npc->behaviorScores.chargeAction = &npc->Behavior->chargeAction;
npc->behaviorScores.circleTargetAction = &npc->Behavior->circleTargetAction;
npc->behaviorScores.lastAttack = &npc->Behavior->lastAttack;
npc->behaviorScores.moveBackAction = &npc->Behavior->moveBackAction;
npc->behaviorScores.shieldWalkAction = &npc->Behavior->shieldWalkAction;
npc->behaviorScores.stopAction = &npc->Behavior->stopAction;
npc->behaviorScores.tauntAction = &npc->Behavior->tauntAction;
}
}
}
UBehaviors.cpp
void UBehaviors::Attack(ANPC* npc)
{
if (npc && npc->currentTarget)
{
// Beginning of items for comparison in Behavior //
// Distance from target //
if (!npc->Behavior->ActionComparisons.Contains(&npc->Behavior->Distance))
{
npc->Behavior->ActionComparisons.Add(&npc->Behavior->Distance, npc->Behavior->False);
}
// Stop Action //
if (!npc->Behavior->ActionComparisons.Contains(&npc->Behavior->stopAction))
{
npc->Behavior->ActionComparisons.Add(&npc->Behavior->stopAction, npc->Behavior->False);
}
// Taunt Action //
if (!npc->Behavior->ActionComparisons.Contains(&npc->Behavior->tauntAction))
{
npc->Behavior->ActionComparisons.Add(&npc->Behavior->tauntAction, npc->Behavior->False);
}
// End of items for comparison in Behavior //
//TODO Change comparison from distance vs threatlevel to distance vs distance checks (Such as if to stop and perform an action)
//once distance decreases enough, call action check (which will be weighted random to randomize distances)
if (npc->Behavior->tickComparisonTimer)
{
npc->Behavior->comparisonTimer++;
if (npc->Behavior->comparisonTimer > 60)
{
bool newHighValue = false;
if (npc->Behavior->checkAction)
{
npc->Behavior->CheckForAction(npc);
}
//Have to give HighValue something to point at to begin with
if (!npc->Behavior->HighValue)
{
UE_LOG(LogTemp, Warning, TEXT("high value is empty"));
npc->Behavior->HighValue = &npc->Behavior->Distance;
npc->Behavior->ActionComparisons&npc->Behavior->Distance] = true;
//TODO remove. in place for testing
npc->movementComponent->MoveTo(npc, npc->currentTarget);
npc->Behavior->checkAction = true;
}
for (auto& item : npc->Behavior->ActionComparisons)
{
if (*item.Key > *npc->Behavior->HighValue)
{
npc->Behavior->HighValue = item.Key;
UE_LOG(LogTemp, Warning, TEXT("The address of HighValue is now %d, with value of %d"), npc->Behavior->HighValue, *npc->Behavior->HighValue);
item.Value = true;
///UE_LOG(LogTemp, Warning, TEXT("The value of %d is now %s"), item.Key, item.Value ? TEXT("True") : TEXT("False"));
newHighValue = true;
} else if (item.Key != npc->Behavior->HighValue)
{
item.Value = false;
///UE_LOG(LogTemp, Warning, TEXT("The value of %d is %s"), item.Key, item.Value ? TEXT("True") : TEXT("False"));
}
}
if (newHighValue)
{
//Set so that a behavior can run through before a new one is called
npc->Behavior->tickComparisonTimer = false;
//Reset so that a new action can be called, whether or not this new behavior has an action
npc->Behavior->actionCalled = false;
npc->Behavior->actionTimer = 0.0f;
//Reset here so as to avoid calling an attack and keep from calling last portion of attack function repeatedly
npc->attacking = false;
newHighValue = false;
}
npc->Behavior->comparisonTimer = 0.0f;
}
}
//Movement to target determination
if (npc->Behavior->ActionComparisons&npc->Behavior->Distance]) //
{
npc->Behavior->checkDistance = false;
npc->Behavior->tickComparisonTimer = true;
}
else if (npc->Behavior->ActionComparisons&npc->Behavior->stopAction] && npc->GetVelocity().Size() > 1)
{
npc->movementComponent->StopActiveMovement();
npc->Behavior->stopAction = 0;
}
//TODO Taunt shouldn't stop other actions, need way to layer over
else if (npc->Behavior->ActionComparisons&npc->Behavior->tauntAction])
{
///UE_LOG(LogTemp, Warning, TEXT("The npc has taunted"));
if (!npc->Behavior->actionCalled)
{
npc->CallAction(2);
npc->Behavior->additionalTaunt = -30;
npc->Behavior->actionCalled = true;
}
if (npc->Behavior->tickComparisonTimer)
{
UE_LOG(LogTemp, Warning, TEXT("The npc has taunted"));
}
}
} else
{
UE_LOG(LogTemp, Warning, TEXT("The npc %s is in UBehaviors::PlayerCombat with no target"), *npc->GetName());
}
}
bool UBehaviors::GetActionValue(ANPC* npc, int32* actionPointer)
{
if (npc->Behavior->ActionComparisons.Contains(actionPointer))
{
///UE_LOG(LogTemp, Warning, TEXT("The value is contained"));
return npc->Behavior->ActionComparisons[actionPointer];
}
return false;
}
void UBehaviors::CheckForAction(ANPC * npc)
{
UE_LOG(LogTemp, Warning, TEXT("Action Check"));
//TODO Within appropriate actions( ie: Stop, walking with shield up), occasional check to move on to new actions
//Perform distance check here
//Actions that can be performed
//Stop
//Taunt
//Circle
//Charge
//Walk with shield up
//Move in for attack
//Move back
//Avoid npc simply walking up to player and letting player attack
if (npc->Behavior->checkDistance)
{
//npc->Behavior->Distance = npc->Scorer->GetDistanceScore(npc, npc->currentTarget);
}
npc->Behavior->stopAction = npc->Scorer->GetStopActionScore(npc, npc->currentTarget) + npc->Behavior->additionalStop;
npc->Behavior->additionalStop = 0;
npc->Behavior->tauntAction = npc->Scorer->GetTauntActionScore(npc, npc->currentTarget) + npc->Behavior->additionalTaunt;
npc->Behavior->additionalTaunt = 0;
npc->Behavior->circleTargetAction = npc->Scorer->GetCircleTargetActionScore(npc, npc->currentTarget) + npc->Behavior->additionalCircle;
npc->Behavior->additionalCircle = 0;
npc->Behavior->chargeAction = npc->Scorer->GetChargeActionScore(npc, npc->currentTarget) + npc->Behavior->addictionalCharge;
npc->Behavior->addictionalCharge = 0;
npc->Behavior->shieldWalkAction = npc->Scorer->GetShieldWalkActionScore(npc, npc->currentTarget) + npc->Behavior->additionalShield;
npc->Behavior->additionalShield = 0;
npc->Behavior->moveBackAction = npc->Scorer->GetMoveBackActionScore(npc, npc->currentTarget) + npc->Behavior->additionalMoveBack;
npc->Behavior->additionalMoveBack = 0;
//Increase the attack action over time so that the npc is more and more likely to attack
npc->Behavior->attackAction += npc->Scorer->GetAttackActionScore(npc, npc->currentTarget) + npc->Behavior->additionalAttack;
npc->Behavior->additionalAttack = 0;
UE_LOG(LogTemp, Warning, TEXT("Comparison timer being set to true in check Action behavior"));
npc->Behavior->tickComparisonTimer = true;
}
UBehaviors.h (child of UCombat)
#pragma once
#include "CoreMinimal.h"
#include "Combat.h"
#include "Behaviors.generated.h"
class APlayer_Char;
/**
*
*/
UCLASS()
class WARHAMMER_API UBehaviors : public UCombat
{
GENERATED_BODY()
//Behaviors are structs as well as functions?
//bool newTest = &test;
public:
static void Wait(ANPC* npc);
void Attack(ANPC* npc);
//Set only by the GetBehavior function within Combat.cpp
EBehavior npcBehavior = EBehavior::NULLBEHAVIOR;
//Comparison of Str and Health between two characters
int32 ThreatLevel = 0;
//Distance to target
int32 Distance = 0;//100
//Score for stopping the npc
int32 stopAction = 0;
int32 additionalStop = 0;
int32 tauntAction = 0;
int32 additionalTaunt = 0;
int32 circleTargetAction = 0;
int32 additionalCircle = 0;
int32 chargeAction = 0;
int32 addictionalCharge = 0;
int32 shieldWalkAction = 0;
int32 additionalShield = 0;
int32 attackAction = 0;
int32 additionalAttack = 0;
int32 moveBackAction = 0;
int32 additionalMoveBack = 0;
//int increased every time npc attacks, decreases attack action score every time it's increased
int32 lastAttack = 0;
bool tickComparisonTimer = true;
bool checkAction = false;
//Bool used to call actions only once
bool actionCalled = false;
//Function used to check value of ActionComparison pair
bool GetActionValue(ANPC* npc, int32* actionPointer);
private:
//Function which compares a given tmap of values
//void CompareValues(ANPC* npc, TMap<int32*, bool*> comparisonMap);
//TMap used to determine major actions within behavior
TMap<int32*, bool> ActionComparisons;
float comparisonTimer = 0.0f;
float actionTimer = 0.0f;
bool checkDistance = true;
int32* HighValue = nullptr;
int32* HighActionScore = nullptr;
bool* True = new bool(true);
bool* False = new bool(false);
APlayer_Char* player = nullptr;
void CheckForAction(ANPC* npc);
void CircleTarget(ANPC* npc, AActor* target);
void AttackTarget(ANPC* npc, AActor* target);
void MoveAwayFromTarget(ANPC* npc, AActor* target);
};
UScores.cpp (child of UCombat)
int32 UScores::GetStopActionScore(ANPC * self, AActor * target)
{
//Given type of npc champion, runt, etc.
//Champions more likely to stop
//if allies are present
//Runts generally always charge in first
int32 score = FMath::RandRange(0,50);
if (self && target)
{
if (self->GetBehaviorActionValue(self, self->behaviorScores.stopAction))
{
UE_LOG(LogTemp, Warning, TEXT("Stop action is true"))
}
if (self->GetNPCType() == self->GetChampionType())
{
score += 20;
}
///UE_LOG(LogTemp, Warning, TEXT("target's velocity is %f"), target->GetVelocity().Size());
if (self->GetVelocity().Size() < 20)
{
return 0;
}
if (target->GetVelocity().Size() > 400)
{
score -= 10;
}
if (target->IsA(ANPC::StaticClass()))
{
ANPC* npcTarget = Cast<ANPC>(target);
}
if (target->IsA(APlayer_Char::StaticClass()))
{
APlayer_Char* playerTarget = Cast<APlayer_Char>(target);
//if player isn't targeting the npc, then charge the player
if (playerTarget && playerTarget->npcTarget != self)
{
score -= 15;
}
}
}
UE_LOG(LogTemp, Warning, TEXT("The score for stop is %d"), score);
return score;
}
int32 UScores::GetTauntActionScore(ANPC * self, AActor * target)
{
int32 score = FMath::RandRange(0, 10);
if (self->GetVelocity().Size() < 1)
{
score += 15;
}
UE_LOG(LogTemp, Warning, TEXT("Returning score for taunt with value of %d"), score);
return score;
}