Browse Source
- Added basic Needs system, which change conditionally and cause new goals to arise. Needs are the driving force of agent goals - Added AgentComponentManager which will be the brain of agents. It's strongly tied to Needs. I have not yet hooked it up to PlanComponentManager, so at the moment it doesn't do anything - Excised Subject Observer from the codebase in favor of compositional, function-pointer-based CallbackContainer. This should reduce multiple inheritance and boilerplate throughout the code while also making behavior more explicit - Made WorldResourceLocator, which is the code from TestWorldResourceLocator (Galavant-Unreal) made into a module, because I don't think I care for passing that around everywhere - Fixed building tests where Logging was broken in tiny ways - Updated Sublime Project with Unreal Debug Editor GDB option - Added two kit pieces to the Building System draftcombatComponentRefactor

18 changed files with 574 additions and 91 deletions
Binary file not shown.
@ -0,0 +1,187 @@ |
|||
#include "AgentComponentManager.hpp" |
|||
|
|||
#include "../../util/Logging.hpp" |
|||
|
|||
#include "../../entityComponentSystem/PooledComponentManager.hpp" |
|||
|
|||
namespace gv |
|||
{ |
|||
AgentComponentManager::AgentComponentManager() : gv::PooledComponentManager<AgentComponentData>(100) |
|||
{ |
|||
} |
|||
|
|||
AgentComponentManager::~AgentComponentManager() |
|||
{ |
|||
} |
|||
|
|||
void AgentComponentManager::Initialize(PlanComponentManager* newPlanComponentManager) |
|||
{ |
|||
PlanManager = newPlanComponentManager; |
|||
} |
|||
|
|||
void AddGoalIfUniqueType(AgentGoalList& goals, AgentGoal& goalToAdd) |
|||
{ |
|||
for (const AgentGoal& goal : goals) |
|||
{ |
|||
if (goalToAdd.Type == goal.Type) |
|||
return; |
|||
} |
|||
|
|||
goals.push_back(goalToAdd); |
|||
} |
|||
|
|||
void AgentComponentManager::Update(float deltaSeconds) |
|||
{ |
|||
EntityList entitiesToUnsubscribe; |
|||
PlanComponentManager::PlanComponentList newPlans; |
|||
|
|||
WorldTime += deltaSeconds; |
|||
|
|||
// TODO: Adding true iterator support to pool will drastically help damning this to hell
|
|||
gv::PooledComponentManager<AgentComponentData>::FragmentedPoolIterator it = |
|||
gv::PooledComponentManager<AgentComponentData>::NULL_POOL_ITERATOR; |
|||
for (gv::PooledComponent<AgentComponentData>* currentComponent = ActivePoolBegin(it); |
|||
currentComponent != nullptr && |
|||
it != gv::PooledComponentManager<AgentComponentData>::NULL_POOL_ITERATOR; |
|||
currentComponent = GetNextActivePooledComponent(it)) |
|||
{ |
|||
if (!currentComponent) |
|||
continue; |
|||
|
|||
Entity currentEntity = currentComponent->entity; |
|||
NeedList& needs = currentComponent->data.Needs; |
|||
AgentGoalList& goals = currentComponent->data.Goals; |
|||
|
|||
if (!currentComponent->data.IsAlive) |
|||
continue; |
|||
|
|||
// Update Needs
|
|||
for (Need& need : needs) |
|||
{ |
|||
if (need.Def) |
|||
{ |
|||
bool needUpdated = false; |
|||
float updateDelta = WorldTime - need.LastUpdateTime; |
|||
while (updateDelta >= need.Def->UpdateRate) |
|||
{ |
|||
need.Level += need.Def->AddPerUpdate; |
|||
|
|||
updateDelta -= need.Def->UpdateRate; |
|||
needUpdated = true; |
|||
} |
|||
|
|||
if (needUpdated) |
|||
{ |
|||
need.LastUpdateTime = WorldTime; |
|||
|
|||
for (const NeedLevelTrigger& needLevelTrigger : need.Def->LevelTriggers) |
|||
{ |
|||
bool needTriggerHit = (needLevelTrigger.GreaterThanLevel && |
|||
need.Level > needLevelTrigger.Level); |
|||
|
|||
if (needTriggerHit) |
|||
{ |
|||
if (needLevelTrigger.NeedsResource && needLevelTrigger.WorldResource) |
|||
{ |
|||
AgentGoal newNeedResourceGoal{AgentGoal::GoalStatus::Initialized, |
|||
AgentGoal::GoalType::GetResource, |
|||
needLevelTrigger.WorldResource}; |
|||
AddGoalIfUniqueType(goals, newNeedResourceGoal); |
|||
} |
|||
else if (needLevelTrigger.DieNow) |
|||
{ |
|||
currentComponent->data.IsAlive = false; |
|||
LOGD_IF(DebugPrint) << "Agent Entity " << currentEntity |
|||
<< " has died!"; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
else |
|||
LOG_ERROR << "Need on entity " << currentEntity << " has no def!"; |
|||
} |
|||
|
|||
// Update head goal
|
|||
if (!goals.empty()) |
|||
{ |
|||
AgentGoalList::iterator headGoalIt = goals.begin(); |
|||
AgentGoal& goal = (*headGoalIt); |
|||
switch (goal.Status) |
|||
{ |
|||
// Start up the goal
|
|||
case AgentGoal::GoalStatus::Initialized: |
|||
// TODO: This IsSubscribed call will go away once PlanManager manages multiple
|
|||
// plans for a single entity. For now, we'll just wait until the entity is no
|
|||
// longer subscribed before adding our goal
|
|||
if (PlanManager && !PlanManager->IsSubscribed(currentEntity)) |
|||
{ |
|||
if (goal.Type == AgentGoal::GoalType::GetResource) |
|||
{ |
|||
LOGD_IF(DebugPrint) |
|||
<< "Agent starting GetResource goal plan (not actually hooked up)"; |
|||
goal.Status = AgentGoal::GoalStatus::InProgress; |
|||
/*gv::PooledComponent<PlanComponentData> newPlanComponent;
|
|||
Htn::Parameter resourceToFind; |
|||
resourceToFind.IntValue = goal.WorldResource; |
|||
resourceToFind.Type = Htn::Parameter::ParamType::Int; |
|||
Htn::ParameterList parameters = {resourceToFind}; |
|||
Htn::TaskCall findAgentCall{testGetResourceTask.GetTask(), parameters}; |
|||
Htn::TaskCallList findAgentTasks = {findAgentCall}; |
|||
newPlanComponent.entity = currentEntity; |
|||
newPlanComponent.data.Tasks.insert(newPlanComponent.data.Tasks.end(), |
|||
GetResourceTasks.begin(), |
|||
GetResourceTasks.end()); |
|||
newPlans.push_back(newPlanComponent);*/ |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
LOG_ERROR << "Agent trying to start goal but no Plan Manager is set!"; |
|||
goals.erase(headGoalIt); |
|||
} |
|||
break; |
|||
case AgentGoal::GoalStatus::Failed: |
|||
case AgentGoal::GoalStatus::Succeeded: |
|||
goals.erase(headGoalIt); |
|||
break; |
|||
default: |
|||
break; |
|||
} |
|||
} |
|||
} |
|||
|
|||
if (PlanManager && !newPlans.empty()) |
|||
PlanManager->SubscribeEntities(newPlans); |
|||
|
|||
UnsubscribeEntities(entitiesToUnsubscribe); |
|||
} |
|||
|
|||
void AgentComponentManager::SubscribeEntitiesInternal(const EntityList& subscribers, |
|||
AgentComponentRefList& components) |
|||
{ |
|||
for (gv::PooledComponent<AgentComponentData>* currentComponent : components) |
|||
{ |
|||
if (!currentComponent) |
|||
continue; |
|||
} |
|||
|
|||
LOGD_IF(DebugPrint) << "AgentComponentManager: Subscribed " << subscribers.size() |
|||
<< " entities"; |
|||
} |
|||
|
|||
void AgentComponentManager::UnsubscribeEntitiesInternal(const EntityList& unsubscribers, |
|||
AgentComponentRefList& components) |
|||
{ |
|||
for (gv::PooledComponent<AgentComponentData>* currentComponent : components) |
|||
{ |
|||
if (!currentComponent) |
|||
continue; |
|||
|
|||
// Perform unsubscription
|
|||
} |
|||
|
|||
LOGD_IF(unsubscribers.size()) << "AgentComponentManager: Unsubscribed " << unsubscribers.size() |
|||
<< " entities"; |
|||
} |
|||
} |
@ -0,0 +1,69 @@ |
|||
#pragma once |
|||
|
|||
#include "../../entityComponentSystem/PooledComponentManager.hpp" |
|||
#include "PlanComponentManager.hpp" |
|||
#include "Needs.hpp" |
|||
|
|||
namespace gv |
|||
{ |
|||
struct AgentGoal |
|||
{ |
|||
enum class GoalStatus |
|||
{ |
|||
None = 0, |
|||
Initialized, |
|||
InProgress, |
|||
Failed, |
|||
Succeeded |
|||
}; |
|||
GoalStatus Status; |
|||
|
|||
enum class GoalType |
|||
{ |
|||
None = 0, |
|||
GetResource, |
|||
|
|||
GoalType_Count |
|||
}; |
|||
GoalType Type; |
|||
|
|||
WorldResourceType WorldResource; |
|||
}; |
|||
typedef std::vector<AgentGoal> AgentGoalList; |
|||
|
|||
struct AgentComponentData |
|||
{ |
|||
bool IsAlive = true; |
|||
NeedList Needs; |
|||
AgentGoalList Goals; |
|||
}; |
|||
|
|||
class AgentComponentManager : public PooledComponentManager<AgentComponentData> |
|||
{ |
|||
private: |
|||
// TODO: Eventually this will be something more complicated
|
|||
float WorldTime; |
|||
|
|||
PlanComponentManager* PlanManager; |
|||
|
|||
protected: |
|||
typedef std::vector<PooledComponent<AgentComponentData>*> AgentComponentRefList; |
|||
|
|||
virtual void SubscribeEntitiesInternal(const EntityList& subscribers, |
|||
AgentComponentRefList& components); |
|||
virtual void UnsubscribeEntitiesInternal(const EntityList& unsubscribers, |
|||
AgentComponentRefList& components); |
|||
|
|||
public: |
|||
typedef std::vector<PooledComponent<AgentComponentData>> AgentComponentList; |
|||
|
|||
bool DebugPrint = false; |
|||
|
|||
AgentComponentManager(); |
|||
virtual ~AgentComponentManager(); |
|||
|
|||
void Initialize(PlanComponentManager* newPlanComponentManager); |
|||
|
|||
virtual void Update(float deltaSeconds); |
|||
}; |
|||
}; |
@ -0,0 +1,45 @@ |
|||
#pragma once |
|||
|
|||
#include <vector> |
|||
|
|||
#include "../../world/WorldResourceLocator.hpp" |
|||
|
|||
namespace gv |
|||
{ |
|||
|
|||
struct NeedLevelTrigger |
|||
{ |
|||
// Conditions
|
|||
bool GreaterThanLevel; |
|||
|
|||
float Level; |
|||
|
|||
// Actions
|
|||
bool NeedsResource; |
|||
WorldResourceType WorldResource; |
|||
|
|||
bool DieNow; |
|||
}; |
|||
|
|||
typedef std::vector<NeedLevelTrigger> NeedLevelTriggerList; |
|||
|
|||
struct NeedDef |
|||
{ |
|||
const char* Name; |
|||
|
|||
float UpdateRate; |
|||
|
|||
float AddPerUpdate; |
|||
|
|||
NeedLevelTriggerList LevelTriggers; |
|||
}; |
|||
|
|||
struct Need |
|||
{ |
|||
NeedDef* Def; |
|||
float Level; |
|||
float LastUpdateTime; |
|||
}; |
|||
|
|||
typedef std::vector<Need> NeedList; |
|||
} |
@ -0,0 +1,52 @@ |
|||
#pragma once |
|||
|
|||
#include <vector> |
|||
|
|||
namespace gv |
|||
{ |
|||
template <class CallbackType> |
|||
struct CallbackCall |
|||
{ |
|||
CallbackType Callback; |
|||
void* UserData; |
|||
}; |
|||
|
|||
template <class CallbackType> |
|||
class CallbackContainer |
|||
{ |
|||
public: |
|||
CallbackContainer() = default; |
|||
~CallbackContainer() = default; |
|||
|
|||
// It's your job to actually call the callbacks
|
|||
std::vector<CallbackCall<CallbackType>> Callbacks; |
|||
|
|||
typename std::vector<CallbackCall<CallbackType>>::iterator FindCallback(const CallbackType callbackToFind, |
|||
void* UserData) |
|||
{ |
|||
for (typename std::vector<CallbackCall<CallbackType>>::iterator callbackIt = Callbacks.begin(); |
|||
callbackIt != Callbacks.end(); ++callbackIt) |
|||
{ |
|||
if ((*callbackIt).Callback == callbackToFind && (*callbackIt).UserData == UserData) |
|||
return callbackIt; |
|||
} |
|||
return Callbacks.end(); |
|||
} |
|||
|
|||
void AddCallback(const CallbackType callbackToAdd, void* UserData) |
|||
{ |
|||
if (callbackToAdd && FindCallback(callbackToAdd, UserData) == Callbacks.end()) |
|||
{ |
|||
Callbacks.push_back({callbackToAdd, UserData}); |
|||
} |
|||
} |
|||
|
|||
void RemoveCallback(const CallbackType callback, void* UserData) |
|||
{ |
|||
typename std::vector<CallbackCall<CallbackType>>::iterator foundCallbackIt = |
|||
FindCallback(callback, UserData); |
|||
if (foundCallbackIt != Callbacks.end()) |
|||
Callbacks.erase(foundCallbackIt); |
|||
} |
|||
}; |
|||
} |
@ -1,49 +0,0 @@ |
|||
#pragma once |
|||
|
|||
#include <vector> |
|||
|
|||
#include "../entityComponentSystem/EntityTypes.hpp" |
|||
|
|||
namespace gv |
|||
{ |
|||
template <class T> |
|||
class Observer |
|||
{ |
|||
public: |
|||
Observer() = default; |
|||
virtual ~Observer() = default; |
|||
|
|||
virtual void OnNotify(const T& event) = 0; |
|||
}; |
|||
|
|||
template <class T> |
|||
class Subject |
|||
{ |
|||
private: |
|||
std::vector<Observer<T>*> Observers; |
|||
|
|||
public: |
|||
Subject() = default; |
|||
~Subject() = default; |
|||
|
|||
void AddObserver(Observer<T>* observer) |
|||
{ |
|||
if (observer && std::find(Observers.begin(), Observers.end(), observer) == Observers.end()) |
|||
Observers.push_back(observer); |
|||
} |
|||
void RemoveObserver(Observer<T>* observer) |
|||
{ |
|||
typename std::vector<Observer<T>*>::iterator foundObserver = |
|||
std::find(Observers.begin(), Observers.end(), observer); |
|||
if (foundObserver != Observers.end()) |
|||
Observers.remove(foundObserver); |
|||
} |
|||
|
|||
// TODO: This could be improved by sending all events in one Notify...
|
|||
void Notify(const T& event) |
|||
{ |
|||
for (Observer<T>* currentObserver : Observers) |
|||
currentObserver->OnNotify(event); |
|||
} |
|||
}; |
|||
}; |
@ -0,0 +1,109 @@ |
|||
#include "GalavantUnreal.h" |
|||
|
|||
#include "TestWorldResourceLocator.hpp" |
|||
#include <limits> |
|||
|
|||
static const unsigned int DEFAULT_RESOURCELIST_SIZE = 10; |
|||
|
|||
typedef std::map<WorldResourceType, ResourceList*> ResourceMap; |
|||
static ResourceMap s_Resources; |
|||
|
|||
void WorldResourceLocator_ClearResources() |
|||
{ |
|||
for (std::pair<const WorldResourceType, ResourceList*>& resourceTypeList : s_Resources) |
|||
{ |
|||
delete resourceTypeList.second; |
|||
} |
|||
s_Resources.clear(); |
|||
} |
|||
|
|||
bool WorldResourceLocator_ResourceListExists(const WorldResourceType type) const |
|||
{ |
|||
return s_Resources.find(type) != s_Resources.end(); |
|||
} |
|||
|
|||
bool WorldResourceLocator_ResourceExistsInWorld(const WorldResourceType type) |
|||
{ |
|||
return WorldResourceLocator_ResourceListExists(type) && !s_Resources[type]->empty(); |
|||
} |
|||
|
|||
void WorldResourceLocator_AddResource(const WorldResourceType type, const gv::Position& location) |
|||
{ |
|||
gv::Position newResource(location); |
|||
// Ensure we're not exactly 0,0,0 because I designed this poorly
|
|||
newResource.Z = !newResource ? 0.1f : newResource.Z; |
|||
|
|||
if (WorldResourceLocator_ResourceListExists(type)) |
|||
{ |
|||
s_Resources[type]->push_back(newResource); |
|||
} |
|||
else |
|||
{ |
|||
ResourceList* newResourceList = new ResourceList(DEFAULT_RESOURCELIST_SIZE); |
|||
newResourceList->push_back(newResource); |
|||
s_Resources[type] = newResourceList; |
|||
} |
|||
} |
|||
|
|||
void WorldResourceLocator_RemoveResource(const WorldResourceType type, const gv::Position& location) |
|||
{ |
|||
if (WorldResourceLocator_ResourceListExists(type)) |
|||
{ |
|||
ResourceList* resourceList = s_Resources[type]; |
|||
ResourceList::iterator resourceIt = |
|||
std::find(resourceList->begin(), resourceList->end(), location); |
|||
if (resourceIt != resourceList->end()) |
|||
resourceList->erase(resourceIt); |
|||
} |
|||
} |
|||
|
|||
void WorldResourceLocator_MoveResource(const WorldResourceType type, |
|||
const gv::Position& oldLocation, |
|||
const gv::Position& newLocation) |
|||
{ |
|||
if (WorldResourceLocator_ResourceListExists(type)) |
|||
{ |
|||
for (gv::Position& currentResource : *s_Resources[type]) |
|||
{ |
|||
// They should be exactly equal. It's the caller's responsibility to keep track of this
|
|||
if (currentResource.Equals(oldLocation, 0.f)) |
|||
{ |
|||
currentResource = newLocation; |
|||
// Ensure we're not exactly 0,0,0 because I designed this poorly
|
|||
currentResource.Z = !currentResource ? 0.1f : currentResource.Z; |
|||
break; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
// Find the nearest resource. Uses Manhattan distance
|
|||
// Manhattan distance of -1 indicates no resource was found
|
|||
gv::Position WorldResourceLocator_FindNearestResource(const WorldResourceType type, |
|||
const gv::Position& location, |
|||
bool allowSameLocation, float& manhattanToOut) |
|||
{ |
|||
gv::Position zeroPosition; |
|||
if (WorldResourceLocator_ResourceListExists(type)) |
|||
{ |
|||
gv::Position closestResource; |
|||
float closestResourceDistance = std::numeric_limits<float>::max(); |
|||
|
|||
for (gv::Position& currentResource : *s_Resources[type]) |
|||
{ |
|||
float currentResourceDistance = location.ManhattanTo(currentResource); |
|||
if (currentResourceDistance < closestResourceDistance && |
|||
(allowSameLocation || currentResourceDistance > 0.f)) |
|||
{ |
|||
closestResourceDistance = currentResourceDistance; |
|||
closestResource = currentResource; |
|||
} |
|||
} |
|||
|
|||
manhattanToOut = closestResourceDistance; |
|||
return closestResource; |
|||
} |
|||
|
|||
manhattanToOut = -1.f; |
|||
return zeroPosition; |
|||
} |
@ -0,0 +1,36 @@ |
|||
#pragma once |
|||
|
|||
#include <map> |
|||
#include <vector> |
|||
|
|||
#include "Position.hpp" |
|||
|
|||
namespace gv |
|||
{ |
|||
enum WorldResourceType |
|||
{ |
|||
None = 0, |
|||
Agent = 1, |
|||
BusStop = 2 |
|||
}; |
|||
|
|||
typedef std::vector<gv::Position> ResourceList; |
|||
|
|||
void WorldResourceLocator_ClearResources(); |
|||
|
|||
bool WorldResourceLocator_ResourceExistsInWorld(const WorldResourceType type); |
|||
|
|||
void WorldResourceLocator_AddResource(const WorldResourceType type, const gv::Position& location); |
|||
void WorldResourceLocator_RemoveResource(const WorldResourceType type, |
|||
const gv::Position& location); |
|||
void WorldResourceLocator_MoveResource(const WorldResourceType type, |
|||
const gv::Position& oldLocation, |
|||
const gv::Position& newLocation); |
|||
|
|||
// Find the nearest resource. Uses Manhattan distance
|
|||
// Manhattan distance of -1 indicates no resource was found
|
|||
gv::Position WorldResourceLocator_FindNearestResource(const WorldResourceType type, |
|||
const gv::Position& location, |
|||
bool allowSameLocation, |
|||
float& manhattanToOut); |
|||
} |
Loading…
Reference in new issue