Skip to content

Instantly share code, notes, and snippets.

@CoffeeVampir3
Last active March 5, 2024 10:32
Show Gist options
  • Save CoffeeVampir3/52ffb0d299d50094273882e79ae5a87e to your computer and use it in GitHub Desktop.
Save CoffeeVampir3/52ffb0d299d50094273882e79ae5a87e to your computer and use it in GitHub Desktop.
cpp_sm ue
// Fill out your copyright notice in the Description page of Project Settings.
#include "Ai/Behaviours/NecroAiBasicCombatant.h"
#include "NavigationSystem.h"
#include "NecroGameplayTags.h"
#include "AbilitySystem/NecroAbilitySystem.h"
#include "ActorInterfaces/CombatInterface.h"
#include "GameFramework/Character.h"
NecroAiState UNecroAiBasicCombatant::BasicCombatant()
{
std::function<NecroAiState()> RandomWander, AcquiredTarget, Attacking;
RandomWander =
[&]()->NecroAiState
{
StateMachine->AddTransition(MakeLazy(AiComponent->ScanForEnemies, AcquisitionRange), AcquiredTarget);
while(true) {
FVector PointResult;
UNavigationSystemV1::K2_GetRandomReachablePointInRadius(GetWorld(),
ControlledCharacter->GetActorLocation(), PointResult, 1000.0f);
co_await StateMachine->WaitForTask(AiComponent->MoveToPoint(PointResult, 50.0f));
}
};
AcquiredTarget = [&]()->NecroAiState
{
const auto ActorCombatInterface = Cast<ICombatInterface>(ControlledCharacter);
auto Target = ActorCombatInterface->GetCombatTargetActor();
check(ActorCombatInterface);
StateMachine->AddTransition([&]()
{
return Target != ActorCombatInterface->GetCombatTargetActor();
}, RandomWander)
.ContinueWith(Attacking);
co_await StateMachine->WaitForTask(AiComponent->MoveToActor(Target, AttackRange-100.0f));
};
Attacking = [&]()->NecroAiState
{
const auto ActorCombatInterface = Cast<ICombatInterface>(ControlledCharacter);
const auto Target = ActorCombatInterface->GetCombatTargetActor();
ActorCombatInterface->SetMotionWarpingCombatTargetActor(Target);
StateMachine->AddTransition([&]()
{
const auto EnemiesInRange = AiComponent->ScanForEnemies(AcquisitionRange);
return !EnemiesInRange || Target != ActorCombatInterface->GetCombatTargetActor();
}, RandomWander)
.ContinueWith(RandomWander)
.OnExit([&ActorCombatInterface]()
{
ActorCombatInterface->ClearMotionWarpingCombatTarget();
});
AbilitySystem->TryActivateAbilitiesByTag(TAGS::ABILITY::SLOT::Primary.GetTag().GetSingleTagContainer());
co_await StateMachine->WaitForTargetedSignal(TAGS::ABILITY::Ability);
};
StateMachine->ChangeToState(RandomWander());
co_await std::suspend_always{};
}
NecroAiState UNecroAiBasicCombatant::ConstructBehaviour()
{
return BasicCombatant();
}
#include "Ai/NecroAiStateMachine.h"
#include "Subsystem/NecroAiSubsystem.h"
void NecroAiStateMachine::Destroy()
{
if(AiSubsystem && OwningObject)
{
AiSubsystem->ClearAllSignals(OwningObject);
}
Reset();
Sleeping = true;
}
void NecroAiStateMachine::ChangeToState(const NecroAiState& NewState)
{
if(CurrentState && OnExitFunc)
{
OnExitFunc();
}
Reset();
CurrentTask = NewState.Handle;
CurrentState = NewState;
}
void NecroAiStateMachine::Reset()
{
FreeEntireCoroutineStack();
CurrentStateTransitions.clear();
CurrentStatelessTasks.clear();
NextState = nullptr;
OnExitFunc = nullptr;
CurrentTask = nullptr;
Sleeping = false;
}
void NecroAiStateMachine::FreeEntireCoroutineStack()
{
while(!CoroutineStack.empty())
{
if(auto Top = CoroutineStack.top())
{
Top.destroy();
}
CoroutineStack.pop();
}
}
void NecroAiStateMachine::ClearSignalBuffers() const
{
AiSubsystem->ClearSignalBuffers(OwningObject);
}
void NecroAiStateMachine::AwaitPush(const coroutine_handle<> NewHandle)
{
if(CurrentTask)
CoroutineStack.push(CurrentTask);
CurrentTask = NewHandle;
}
bool NecroAiStateMachine::AwaitSignal(const FGameplayTag Tag)
{
const auto Result= AiSubsystem->AwaitTargetedSignal(OwningObject, Tag, [this]()
{
if(!this) return;
this->Sleeping = false;
});
//The signal was buffered, resume immediately.
if(Result) return false;
//Not ready, go to sleep.
Sleeping = true;
return true;
}
std::function<NecroAiState()> NecroAiStateMachine::CheckNextTransition()
{
if(CurrentStateTransitions.empty()) return nullptr;
auto &[TransFunc, StateFunc] = CurrentStateTransitions.front();
if(TransFunc()) return StateFunc;
//Rotate our deque so the transition we just evaluated is now the back.
CurrentStateTransitions.push_back(CurrentStateTransitions.front());
CurrentStateTransitions.pop_front();
return nullptr;
}
bool NecroAiStateMachine::Run()
{
check(AiSubsystem)
check(OwningObject)
if(Sleeping) return true;
if(!CurrentState)
{
if(NextState)
{
//We have a valid next state, so we move to that.
ChangeToState(std::move(NextState()));
return true;
}
return false;
}
for(const auto& StatelessTask : CurrentStatelessTasks)
{
StatelessTask();
}
//Check trasition, if we should transition run any exit code and switch states.
if(const auto CheckResult = CheckNextTransition())
{
ChangeToState(CheckResult());
}
//If we have no valid task or the current one is done, pop the stack until we have a valid task or return false.
while(!CurrentTask || CurrentTask.done()) {
if(CoroutineStack.empty())
{
if(NextState)
{
//We have a valid next state, so we move to that.
ChangeToState(std::move(NextState()));
return true;
}
//CurrentState.ExitAndDestroy();
return false;
}
if(CurrentTask)
{
CurrentTask.destroy();
}
CurrentTask = CoroutineStack.top();
CoroutineStack.pop();
}
CurrentTask.resume();
return true;
}
//Transition on true.
NecroAiStateMachine& NecroAiStateMachine::AddTransition(const std::function<bool()>& TransitionFunc,
const std::function<NecroAiState()>& StateConstructor)
{
CurrentStateTransitions.push_back(TransitionBundle{TransitionFunc, StateConstructor});
return *this;
}
NecroAiStateMachine& NecroAiStateMachine::ContinueWith(const std::function<NecroAiState()>& StateConstructor)
{
NextState = StateConstructor;
return *this;
}
NecroAiStateMachine& NecroAiStateMachine::OnExit(const std::function<void()>& Finalizer)
{
OnExitFunc = Finalizer;
return *this;
}
TaskAwaiter NecroAiStateMachine::WaitForTask(NecroAiTask&& TaskToAwait)
{
return TaskAwaiter{*this, std::forward<NecroAiTask>(TaskToAwait)};
}
SignalAwaiter NecroAiStateMachine::WaitForTargetedSignal(const FGameplayTag TagToAwait)
{
return SignalAwaiter{*this, TagToAwait};
}
NecroAiStateMachine& NecroAiStateMachine::AddStatelessTask(const std::function<void()>& Task)
{
CurrentStatelessTasks.push_back(std::move(Task));
return *this;
}
// Fill out your copyright notice in the Description page of Project Settings.
#include "Ai/Behaviours/NecroAiWanderBehaviour.h"
#include "NavigationSystem.h"
#include "GameFramework/Character.h"
NecroAiState UNecroAiWanderBehaviour::RandomWander() const
{
while(true)
{
FVector PointResult;
UNavigationSystemV1::K2_GetRandomReachablePointInRadius(GetWorld(),
ControlledCharacter->GetActorLocation(), PointResult, WanderRadius);
co_await StateMachine->WaitForTask(AiComponent->MoveToPoint(PointResult, AcceptanceRadius));
}
}
NecroAiState UNecroAiWanderBehaviour::ConstructBehaviour()
{
return RandomWander();
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment