Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions .vscode/c_cpp_properties.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"configurations": [
{
"name": "Win32",
"includePath": [
"${workspaceFolder}/**"
],
"defines": [
"_DEBUG",
"UNICODE",
"_UNICODE"
],
"windowsSdkVersion": "10.0.18362.0",
"compilerPath": "C:/Program Files (x86)/Microsoft Visual Studio/2019/Community/VC/Tools/MSVC/14.22.27905/bin/Hostx64/x64/cl.exe",
"cStandard": "c11",
"cppStandard": "c++17",
"intelliSenseMode": "msvc-x64"
}
],
"version": 4
}
54 changes: 54 additions & 0 deletions Source/NujasAI/NujasAI.Build.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// Copyright 2018-2019 The Janus Project | 2034 Complex LLC. All Rights Reserved.

using UnrealBuildTool;

public class NujasAI : ModuleRules
{
public NujasAI(ReadOnlyTargetRules Target) : base(Target)
{
PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs;

PublicIncludePaths.AddRange(
new string[] {
// ... add public include paths required here ...
}
);


PrivateIncludePaths.AddRange(
new string[] {
// ... add other private include paths required here ...
"NujasAI/Private"
}
);


PublicDependencyModuleNames.AddRange(
new string[]
{
"Core",
// ... add other public dependencies that you statically link with here ...
}
);


PrivateDependencyModuleNames.AddRange(
new string[]
{
"CoreUObject",
"Engine",
"Slate",
"SlateCore",
// ... add private dependencies that you statically link with here ...
}
);


DynamicallyLoadedModuleNames.AddRange(
new string[]
{
// ... add any modules that your module loads dynamically here ...
}
);
}
}
106 changes: 106 additions & 0 deletions Source/NujasAI/Private/AggressionScoreComponent.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
// Copyright 2018-2019 The Janus Project | 2034 Complex LLC. All Rights Reserved.

#include "AggressionScoreComponent.h"
#include "GameFramework/Character.h"
#include "Kismet/GameplayStatics.h"
#include "NujasAIGlobals.h"
#include "TimerManager.h"

UAggressionScoreComponent::UAggressionScoreComponent()
{
PrimaryComponentTick.bCanEverTick = false;
}

void UAggressionScoreComponent::BeginPlay()
{
Super::BeginPlay();
Owner = Cast<ACharacter>(this->GetOwner());
InvalidateAggressionScoreTimer();
}

void UAggressionScoreComponent::BeginAggressionSystem(const TArray<AActor*> BotsToConsider)
{
if(Owner)
{
Bots = BotsToConsider;

Owner->GetWorldTimerManager().SetTimer
(
AggressionScoreHandle,
this,
&UAggressionScoreComponent::CalculateAggressionScores,
1.f,
true
);
}
}

void UAggressionScoreComponent::StopAggressionSystem()
{
InvalidateAggressionScoreTimer();
HeapAggresiveActors.Empty();
Bots.Empty();
}

void UAggressionScoreComponent::AddBot(AActor* const Bot)
{
Bots.Add(Bot);
}

void UAggressionScoreComponent::CalculateAggressionScores()
{
if (Bots.Num() > 0)
{
HeapAggresiveActors.Empty();

// do the aggression scoring here
for (AActor* const Bot : Bots)
{
/*
* Removing a specific bot from anywhere in the list is expensive.
* Better to just check if still valid.
* The list doesn't grow larger than 20 items per battle.
*/
if (!Bot) continue;
float Score = IAggressive::Execute_CanBecomeAggressive(Bot) ? 1.0 : 0.0;
Score += IAggressive::Execute_GetAggressionPriority(Bot);

if (IsPlayerCharacter)
{
// is bot on screen? (Viewport utility)
// angle from camera?
}

// distance from actor

HeapAggresiveActors.Add(FAggressiveActorEntry(Bot, Score));

}

// heapify

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Not really necessary to heapify. I could just sort them based on their priority and be done with it


// Pop items and make them aggressive until run out of tokens

// rest should be made calm
}
}

inline void UAggressionScoreComponent::InvalidateAggressionScoreTimer()
{
if (Owner)
{
Owner->GetWorldTimerManager().ClearTimer(AggressionScoreHandle);
}
}

FAggressiveActorEntry::FAggressiveActorEntry()
{
this->Score = 0;
this->Bot = nullptr;
}

FAggressiveActorEntry::FAggressiveActorEntry(AActor* Bot, float Score)
{
this->Bot = Bot;
this->Score = Score;
}
20 changes: 20 additions & 0 deletions Source/NujasAI/Private/NujasAIModule.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// Copyright 2018-2019 The Janus Project | 2034 Complex LLC. All Rights Reserved.

#include "NujasAIModule.h"

#define LOCTEXT_NAMESPACE "FNujasAIModule"

void FNujasAIModule::StartupModule()
{
// This code will execute after your module is loaded into memory; the exact timing is specified in the .uplugin file per-module
}

void FNujasAIModule::ShutdownModule()
{
// This function may be called during shutdown to clean up your module. For modules that support dynamic reloading,
// we call this function before unloading the module.
}

#undef LOCTEXT_NAMESPACE

IMPLEMENT_MODULE(FNujasAIModule, NujasAI)
96 changes: 96 additions & 0 deletions Source/NujasAI/Public/AggressionScoreComponent.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
#pragma once

#include "CoreMinimal.h"
#include "Components/ActorComponent.h"
#include "AggressionScoreComponent.generated.h"


class ACharacter;

USTRUCT()
struct FAggressiveActorEntry
{
GENERATED_BODY()

FAggressiveActorEntry();
FAggressiveActorEntry(AActor* Bot, float Score);

AActor* Bot;
float Score;
};

static const uint32 DEFAULT_AGGRESSION_TOKENS = 14;

UCLASS(ClassGroup = (NujasAI), meta = (BlueprintSpawnableComponent), HideCategories=(Tags, Cooking, Collision, ComponentReplication, Activation, AssetUserData))
class NUJASAI_API UAggressionScoreComponent : public UActorComponent
{
GENERATED_BODY()

// each enemy with a specific interface should be evaluated periodically (on a timer) by this component
//
// each AI has the opportunity to become aggressive based on parameters listed below
// by default, every AI must not be aggressive
//
// each timer iteration all enemies with the Aggressive Interface must be collected, regardless of whether they are aggressive or not
// every collected actor will be re-evaluated on whether they should remain aggressive or not
//
// every ai has an aggression score count and the count will be evaluated based on there paramters:
// - Primary -> Can I become aggressive? (not in hit reaction or in a state blocking them indefinetely)
// - Secondary -> Aggression Rank based on distance to target
// - Misc -> Action Rank: a) "on/off screen" b) angle "from camera" (needs to be toggled) c) distance from target
//
// once calculated, send it to a max heap of structs that stores the aggression score and the object
// once every enemy is in the heap, pop the actors and force them into aggression until run out of available tokens
//
// every other actor should be immediately set to calm

// todo: add player current target later when start revamping the targeting mechanic

void CalculateAggressionScores();

inline void InvalidateAggressionScoreTimer();

UPROPERTY()
// heapify array to sort bots based on their aggression score
TArray<FAggressiveActorEntry> HeapAggresiveActors;

UPROPERTY()
// the bots that will be considered for calculating the aggression score
TArray<AActor*> Bots;

UPROPERTY()
FTimerHandle AggressionScoreHandle;

UPROPERTY()
ACharacter* Owner;

public:
// Sets default values for this component's properties
UAggressionScoreComponent();

UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Aggression Params", meta = (ClampMin = "0", ClampMax = "100"))
// How many tokens can the aggression system give out for Bots to become aggressive
int32 MaxAggressionTokens = DEFAULT_AGGRESSION_TOKENS;

UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Aggression Params")
// Is this component attached to the player character
bool IsPlayerCharacter = true;

protected:
// Called when the game starts
virtual void BeginPlay() override;

public:
UFUNCTION(BlueprintCallable, Category = "Aggression")
void BeginAggressionSystem(const TArray<AActor*> BotsToConsider);

UFUNCTION(BlueprintCallable, Category = "Aggression")
void StopAggressionSystem();

UFUNCTION(BlueprintCallable, Category = "Aggression")
/*
* The system can only add bots.
* When the encounter is over, the system will clean out leftover garbage.
*/
void AddBot(AActor* const Bot);
};
38 changes: 38 additions & 0 deletions Source/NujasAI/Public/NujasAIGlobals.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// Copyright 2018-2019 The Janus Project | 2034 Complex LLC. All Rights Reserved.

#pragma once

#include "Interface.h"
#include "NujasAIGlobals.generated.h"

UINTERFACE(Blueprintable)
class NUJASAI_API UAggressive : public UInterface

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

There are a total of 96 results for : public UInterface https://github.com/EpicGames/UnrealEngine/search?q=%22+%3A+public+UInterface%22&unscoped_q=%22+%3A+public+UInterface%22

There are 59 resutls for Interface : public UInterface
https://github.com/EpicGames/UnrealEngine/search?q=%22Interface+%3A+public+UInterface%22&unscoped_q=%22Interface+%3A+public+UInterface%22

This shows that the naming convention with the variable name postfixed with Interface is the majority

Since the PlatformInterface we have is also using the majority convention: https://github.com/nujas/TheJanusFramework/blob/f7f455342b102c86f840e0c201f9d1dbb2295e33/Source/NujasCore/Public/PlatformInfoInterface.h

I would suggest rename this to UAggressiveInterface

{
GENERATED_BODY()
};

class IAggressive
{
GENERATED_BODY()

public:
// callback for when the actor turns aggressive
UFUNCTION(BlueprintImplementableEvent, Category = "Aggressive")
bool OnTurAgressive();

// callback when the actor is deemed none-aggressive
UFUNCTION(BlueprintImplementableEvent, Category = "Aggressive")
bool OnTurnCalm();

// helper func to determine the AI's priority. The priority is usually set manually depending on the AI

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

This value is also set based on distance. i.e the designer could say that a draugr's priority at 5 meters away from the target is 10 but at 100 meters the priority is 1.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

That value is determined on the Bot's side. This function just returns it

UFUNCTION(BlueprintImplementableEvent, Category = "Aggressive")
int32 GetAggressionPriority();

// helper func to tell how many tokens does an AI take.
UFUNCTION(BlueprintImplementableEvent, Category = "Aggressive")
int32 GetTokenTax();

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

A static number that a designer could set on the Bot's side. Usually, the Tax should never change during the runtime of the game


// helper func to determine if the actor is in a state that would prevent them from becoming aggressive
UFUNCTION(BlueprintImplementableEvent, Category = "Aggressive")
bool CanBecomeAggressive();

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Evaluated Bot Side. Examples when the AI cannot become aggressive: Stunned, Dead, Stuck, etc.

};
14 changes: 14 additions & 0 deletions Source/NujasAI/Public/NujasAIModule.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// Copyright 2018-2019 The Janus Project | 2034 Complex LLC. All Rights Reserved.

#pragma once

#include "CoreMinimal.h"
#include "Modules/ModuleManager.h"

class FNujasAIModule : public IModuleInterface
{
public:
/** IModuleInterface implementation */
virtual void StartupModule() override;
virtual void ShutdownModule() override;
};
Loading