Download

Rooting out a disturbance in the force (code)

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;
}




Any help from the devs?

From testing, it seems that the more functions it calls in UBehaviors that are from UScores, the quicker it crashes. But unfortunately, even with all UScores functions commented out, it eventually crashes, whether it’s from clicking on a character while playing, or simply letting it run.
I just don’t know what to do from here, or why this is happening.

In case anyone takes a look, here are notes I made while trying to find the cause within UBehaviors::Attack. If I don’t run that function out of UCombat everything is fine.

"Seems to crash after compile, regardless of what changes are
Crashed during pie after commenting out the for loop
relaunched and played without re compiling, no crashes
crashed during pie after uncommenting and compiling for loop
relaunched and played without re compiling, no crashes for first play (around 10 minutes) crash on selecting npc(after ejecting, clicking, possessing numerous times) during second play (around 15) minutes
With everything uncommented, continuous crashes outside of pie or pressing stop
Examples:
-Pressing stop during pie
-Clicking on npc after pie
-Clicking back into editor after it was running in background

Crash seconds after pie
After clean/rebuild solution, 3x pie no crash
no crash after small change in code, compile
Crash after compile with small change in code
Cleaning/Rebuild solution
Crashing after 5+ minutes of pie, ejecting and letting npc run through code then possessing, then re ejecting to select npc"

What is APlayer_Char? Is it an UObject derived class? Because if so, you need to add the UPROPERTY() macro above it otherwise that reference might be garbage collected and cause a crash.

No, APlayer_Char is derived from APlayerController :confused:
Thank you though. I don’t have an education in programming, just learned what I’ve needed to to get things I want to work, so I’m missing what’s like a lot of important knowledge.
What is garbage collection exactly and what sort of code in ue4 might lead to it?
UBehaviors, UScores and UCombat are all UObject derived, and at the start of the code a new object is made for both UBehaviors and UScores with the npc being passed in. Could that have a negative effect?

Read this for more information on the subject: https://docs.unrealengine.com/en-us/Programming/UnrealArchitecture/Objects

Thanks to you I believe I’ve found the problem. I made the npc’s objects for UBehaviors and UScores both a UPROPERTY() since you mentioned that UObjects can be garbage collected, and I haven’t crashed yet.
So big thanks for making me aware of garbage collection!