Browse Source

Merge branch 'hierarchicalTaskNetworks'

Merging development branch for HTNs into master. I think they're close enough that I can start working with them
combatComponentRefactor
Macoy Madson 6 years ago
parent
commit
1370f703ef
  1. 15
      .gitignore
  2. 6
      .gitmodules
  3. 33
      Jamrules
  4. 39
      README.md
  5. 34
      assets/designDocs/technical/behaviorTrees.txt
  6. 82
      assets/designDocs/technical/hierarchicalTaskNetworks.txt
  7. 2
      src/GalavantMain.cpp
  8. 2
      src/Jamfile
  9. 5
      src/ai/Jamfile
  10. 416
      src/ai/htn/HTNPlanner.cpp
  11. 96
      src/ai/htn/HTNPlanner.hpp
  12. 114
      src/ai/htn/HTNTasks.cpp
  13. 107
      src/ai/htn/HTNTasks.hpp
  14. 43
      src/ai/htn/HTNTypes.hpp
  15. BIN
      src/experiments/flatbuffers/SavedHello.bin
  16. BIN
      src/experiments/flatbuffers/testFlatbuffers_write
  17. 95
      src/objectComponent/Component.cpp
  18. 89
      src/objectComponent/Component.hpp
  19. 54
      src/objectComponent/ComponentManager.cpp
  20. 46
      src/objectComponent/ComponentManager.hpp
  21. 10
      src/objectComponent/ComponentTypes.hpp
  22. 169
      src/objectComponent/Object.cpp
  23. 113
      src/objectComponent/Object.hpp
  24. 381
      src/objectComponent/ObjectComponentManager.cpp
  25. 120
      src/objectComponent/ObjectComponentManager.hpp
  26. 16
      src/objectComponent/ObjectID.cpp
  27. 15
      src/objectComponent/ObjectID.hpp
  28. 5
      src/objectComponent/ObjectType.cpp
  29. 25
      src/objectComponent/ObjectType.hpp
  30. 4
      src/objectComponent/README.txt
  31. 26
      src/project/galavantSublime/galavant.sublime-project
  32. 331
      src/unitTesting/HTN_test.cpp
  33. 14
      src/unitTesting/Jamfile
  34. 167
      src/unitTesting/ObjectComponent_test.cpp
  35. 51
      src/unitTesting/Position_test.cpp
  36. 5
      src/world/Jamfile
  37. 101
      src/world/Position.cpp
  38. 38
      src/world/Position.hpp
  39. 1
      thirdParty/Catch
  40. 2
      thirdParty/flatbuffers

15
.gitignore

@ -27,13 +27,11 @@
*.out
*.app
# Codeblocks folder ignores
# Build folder ignores
bin/
obj/
lib/
# Codeblocks file ignores
*.layout
# Ignore documentation (can be autogenerated via doxygen)
docs/
@ -41,8 +39,17 @@ docs/
# Ignore anything with the LOCAL prefix
LOCAL*
#
# My Workflow Ignores
#
# Codeblocks file ignores
*.layout
# Ignore sublime workspace
*.sublime-workspace
CppCheckResults.xml
ValgrindOut.xml
ValgrindOut.xml
*.ycm_extra_conf.py

6
.gitmodules

@ -0,0 +1,6 @@
[submodule "thirdParty/flatbuffers"]
path = thirdParty/flatbuffers
url = https://github.com/google/flatbuffers.git
[submodule "thirdParty/Catch"]
path = thirdParty/Catch
url = https://github.com/philsquared/Catch.git

33
Jamrules

@ -1,11 +1,34 @@
##
## Compiler
##
CC = gcc ;
C++ = g++ ;
LINK = g++ ;
# GCC
#C++ = g++ ;
#LINK = g++ ;
# Clang
C++ = clang++ ;
LINK = clang++ ;
##
## Compiler arguments
##
# Galavant requires C++11
# fPIC = position independent code. This is so the lib can be linked in other libs/executables
C++FLAGS = -std=c++11 -fPIC -g ;
OBJECTC++FLAGS = -std=c++11 ;
# fPIC = position independent code. This is so the lib can be linked in other libs/executables
# g = debug symbols
# lstdc++ = standard library
# -stdlib=lib++ = Unreal uses libc++, which is busted on Ubuntu 16.04. Remove this option
# if you don't give a shit about Unreal and/or want to build the tests
# Og = compile without optimizations for debugging
# -Wall -Wextra = Error detection/tolerance
#C++FLAGS = -std=c++11 -stdlib=libc++ -fPIC -g -Og -Wall -Wextra ; # Required arguments for Unreal
C++FLAGS = -std=c++11 -fPIC -g -Og -Wall -Wextra ; # Required arguments for tests
OBJECTC++FLAGS = -std=c++11 -lstdc++ -g -Og ;
HDRS = thirdParty/flatbuffers/include ;

39
README.md

@ -7,26 +7,37 @@ With my current design, agents are based on Maslow's hierarchy of needs. This ma
Galavant is basically [Horizon](http://github.com/makuto/horizon) v2. I coded myself into a bit of a quagmire in Horizon, so I decided I would be more productive if I started over.
Interested?
------------
Message me on Github or email me at macoymadson@gmail.com for any questions. I'd love some help!
## Building
There is a lot to the design of Galavant that does not fit in this readme. If you email me, I can fill you in on my goals with Galavant and what the game will actually be like.
Galavant uses Flatbuffers. Before trying to build, make sure to initialize it:
`git submodule update --init --recursive`
Then run cmake in `thirdParty/flatbuffers`:
`cmake .`
Then
`make`
License
--------
The code is MIT licensed. I intend on keeping all data (images, sprites, gameplay/design data) private, following the Doom/Quake model, but for now, consider those to be MIT Licensed.
Jam is used as the build system for Galavant. I used to use makefiles in the Horizon iteration of this project, and I hated them. Jam is much easier to use and takes much less work to get a project set up. If people actually become interested in this project, I can invest some time in cmake-ifying it for more standard building. If you're willing to try building using Jam, you may have to edit Jamrules to set your preferred compiler (the default is g++).
Technical
----------
I am writing Galavant in C++ (C++11, to be specific). I rely on SFML2 for sound, graphics, input, and networking. SFML is wrapped in my personal game library, [Base2.0](https://github.com/makuto/base2.0) (also MIT licensed), which also has other useful game-agnostic code.
To build with jam, first install jam:
`sudo apt-get install jam`
Then run jam (in galavant/ is fine):
`jam`
Jam is used as the build system for Galavant. I used to use makefiles in the Horizon iteration of this project, and I hated them. Jam is much easier to use and takes much less work to get a project set up. If people actually become interested in this project, I can invest some time in cmake-ifying it for more standard building. If you're willing to try building using jam, you may have to edit Jamrules to set your preferred compiler.
## Dependencies
Dependencies
--------------
Galavant uses features of C++11.
The following libraries are required by Galavant and included in /thirdParty:
- [OpenSimplexNoise](https://gist.github.com/tombsar/716134ec71d1b8c1b530), created by Arthur Tombs (public domain)
- [Flatbuffers](https://github.com/google/flatbuffers), created by Google/Fun Propulsion Labs (Apache License, v2.0)
- [Flatbuffers](https://github.com/google/flatbuffers), created by Google/Fun Propulsion Labs (Apache License, v2.0)
- [Catch](https://github.com/philsquared/Catch), created by various contributors/a dude named Travis (Boost Software License)
## License
The code is MIT licensed. I intend on keeping all data (images, sprites, gameplay/design data) private, following the Doom/Quake model, but for now, consider those to be MIT Licensed.
## Want to Get Involved?
Message me on Github or email me at macoymadson@gmail.com for any questions. I'd love some help!
There is a lot to the design of Galavant that does not fit in this readme. If you email me, I can fill you in on my goals with Galavant and what the game will actually be like.

34
assets/designDocs/technical/behaviorTrees.txt

@ -0,0 +1,34 @@
Behavior Trees
====================
- Leaf: Cannot have children
- Conditional: returns success or failure immediately
- Action: can return running
- Composite: Has one or more children
- Parallel: runs multiple children at once
- Sequence: runs children in order, aborts on child failure
- Selector: runs children in order, continues on child failure
- Random: runs one of its children at random
- Decorator: Has exactly one child
- Inverter
- Succeeder: always return success
- Failer: always return failure
- Repeat X times
- Repeat until failure
- Repeat until success
Behavior Tree
ReceiveStatuses(Node, (TreeInstances, Statuses))
RunChildren(TreeInstances, ChildNodes)
Update
loopCounter
do
update trees which have nodes which aren't running
while there are trees which aren't running and not at max number loops
tick running nodes

82
assets/designDocs/technical/hierarchicalTaskNetworks.txt

@ -0,0 +1,82 @@
Tasks
Something to be done which can be broken down into a method to accomplish (task sort of equals goal)
Methods
If the agent's state matches a set of preconditions, return a list of methods and primitives which complete the task
Primitives/Operators
If the agent's state matches a set of preconditions, return that the primitive can be performed
Perform the actual change in the world
Change the world state (this can be both during planning and after actually performing the primitive task) (should
these be separated?)
agent = entityID
agentState = struct
{
isUndead = false
hasAmmo = false
target = entityID (should this in agent state or parameters?)
}
worldState = struct
{
towns = {townA: [x, y, z, properties], townB: [x, y, z, properties]}
canUndeadDoShit = true
}
parameters = vector of param
param = struct
{
enum type
union
{
entityID entity
coordinate coord
scalar val
vector vec
float floatVal
}
}
MethodA = 0
MethodB = 1
PrimitiveA = 0
PrimitiveB = 1
MethodA = struct
{
preconditionsSatisfied(agent, agentState, worldState, parameters)
{
return worldState.useMethodA
}
newTasks = [(1, method, [param1, param2]), (0, primitive, [])];
}
PrimitiveA = module
{
arePreconditionsSatisfied(agent, agentState, worldState, parameters)
{
return worldState.useMethodA
}
applyEffects(agent, agentState, worldState, parameters)
{
worldState.hasDonePrimitiveA = true
}
execute(agent, agentState, worldState, parameters)
{
if preconditionsSatisfied(agent, agentState, worldState, parameters)
primitiveAComponentManager.subscribeTaskAgent(agent, this) // pass self in for callback (instead: event listener?)
return true
return false
}
PrimitiveADoneCallback(agent, bool succeeded)
{
// plan follow code will call applyEffects ?
notifyPlanFollowCode(agent, succeeded)
}
}

2
src/GalavantMain.cpp

@ -4,5 +4,5 @@
void gv::GalavantMain::Update(float frameTime)
{
std::cout << "Updated Galavant\n";
std::cout << "Updated Galavant frameTime: " << frameTime << "\n";
}

2
src/Jamfile

@ -8,6 +8,8 @@ MakeLocate libGalavant.a : lib ;
SubInclude . src thirdPartyWrapper ;
SubInclude . src entityComponentSystem ;
SubInclude . src ai ;
SubInclude . src world ;
# Experiments and Testing (feel free to remove these if you don't want them built)
SubInclude . src experiments ;

5
src/ai/Jamfile

@ -0,0 +1,5 @@
SubDir . src ai ;
Library libGalaAi : htn/HTNTasks.cpp htn/HTNPlanner.cpp ;
MakeLocate libGalaAi.a : lib ;

416
src/ai/htn/HTNPlanner.cpp

@ -0,0 +1,416 @@
#include "HTNPlanner.hpp"
#include "HTNTypes.hpp"
#include "HTNTasks.hpp"
#include <iostream>
namespace Htn
{
bool DecomposeGoalTask(GoalDecompositionStack& decompositionStack, GoalTask* goalTask,
int methodIndex, ParameterList& parameters, WorldState& state)
{
GoalDecomposition decomposition;
decomposition.DecomposedGoalTask = goalTask;
decomposition.MethodIndex = methodIndex;
decomposition.Parameters = parameters;
decomposition.InitialState = state;
// Perform the decomposition
if (goalTask->DecomposeMethodAtIndex(decomposition.CallList, decomposition.MethodIndex,
decomposition.Parameters))
{
decompositionStack.push_back(decomposition);
return true;
}
return false;
}
bool DecomposeCompoundTask(TaskCallList& compoundDecompositions, CompoundTask* compoundTask,
const WorldState& state, const ParameterList& parameters)
{
if (!compoundTask->StateMeetsPreconditions(state, parameters))
return false;
return compoundTask->Decompose(compoundDecompositions, state, parameters);
}
// When the stack is empty, find a goal task to push onto the task or add tasks as per usual
// If any Decompositions or Preconditions fail, we must fail the entire plan because we have
// no alternate methods
Planner::Status Planner::PlanStep_BottomLevel(void)
{
if (InitialCallList.empty())
return Status::Failed_NoTasks;
else if (WorkingCallList.empty() && FinalCallList.empty())
{
// First time the planner has been stepped
WorkingCallList = InitialCallList;
StacklessState = State;
}
else if (WorkingCallList.empty() && !FinalCallList.empty())
return Status::PlanComplete;
TaskCallList compoundDecompositions;
if (DebugPrint)
{
std::cout << "\nPlanStep()\nWorkingCallList.size() = " << WorkingCallList.size() << "\n";
printTaskCallList(WorkingCallList);
}
for (TaskCallListIterator currentTaskCallIter = WorkingCallList.begin();
currentTaskCallIter != WorkingCallList.end();
currentTaskCallIter = WorkingCallList.erase(currentTaskCallIter))
{
TaskCall currentTaskCall = (*currentTaskCallIter);
Task* currentTask = currentTaskCall.TaskToCall;
if (!currentTask)
continue;
TaskType currentTaskType = currentTask->GetType();
if (DebugPrint)
std::cout << "TaskType currentTaskType = " << (int)currentTaskType << "\n";
switch (currentTaskType)
{
case TaskType::Goal:
{
if (DebugPrint)
std::cout << "Goal\n";
GoalTask* goalTask = currentTask->GetGoal();
if (!DecomposeGoalTask(DecompositionStack, goalTask, 0, currentTaskCall.Parameters,
StacklessState))
return Status::Failed_NoPossiblePlan;
WorkingCallList.erase(currentTaskCallIter);
return Status::Running_SuccessfulDecompositionStackPush;
}
break;
case TaskType::Primitive:
{
if (DebugPrint)
std::cout << "Primitive\n";
PrimitiveTask* primitiveTask = currentTask->GetPrimitive();
if (!primitiveTask->StateMeetsPreconditions(StacklessState,
currentTaskCall.Parameters))
return Status::Failed_NoPossiblePlan;
// If at top level, apply primitive tasks immediately to the stackless state
primitiveTask->ApplyStateChange(StacklessState, currentTaskCall.Parameters);
FinalCallList.push_back(currentTaskCall);
if (BreakOnPrimitiveApply)
{
WorkingCallList.erase(currentTaskCallIter);
return Status::Running_SuccessfulPrimitive;
}
// Keep processing tasks otherwise
}
break;
case TaskType::Compound:
{
if (DebugPrint)
std::cout << "Compound\n";
CompoundTask* compoundTask = currentTask->GetCompound();
// we need to push our decomposition to our call list, but we're iterating on
// it. By pushing our decomposition to compoundDecompositions in
// DecomposeCompoundTask(), we'll then break out of the loop and tack it on there
if (!DecomposeCompoundTask(compoundDecompositions, compoundTask, StacklessState,
currentTaskCall.Parameters))
return Status::Failed_NoPossiblePlan;
currentTaskCallIter = WorkingCallList.erase(currentTaskCallIter);
}
break;
default:
return Status::Failed_BadData;
}
if (compoundDecompositions.size())
{
if (DebugPrint)
{
std::cout << "compoundDecompositions.size() = " << compoundDecompositions.size()
<< "\n";
}
WorkingCallList.insert(WorkingCallList.begin(), compoundDecompositions.begin(),
compoundDecompositions.end());
if (DebugPrint)
std::cout << "PlanStep Done\n";
// We have to break here because we are adding things to the list we're iterating
// on; We'll process the tasks next Step
return Status::Running_SuccessfulDecomposition;
}
if (DebugPrint)
std::cout << "Loop Done\n";
}
return Status::PlanComplete;
}
Planner::Status Planner::PlanStep_StackFrame(void)
{
// Remember: If goal fails to decompose and goal is bottom of stack, fail
GoalDecompositionStack::iterator currentStackFrameIter = DecompositionStack.end() - 1;
GoalDecomposition& currentStackFrame = *currentStackFrameIter;
if (DebugPrint)
{
std::cout << "\nPlanStep()\ncurrentStackFrame.CallList.size() = "
<< currentStackFrame.CallList.size() << "\n";
printTaskCallList(currentStackFrame.CallList);
std::cout << "Stack Depth: ";
for (unsigned int i = 0; i < DecompositionStack.size(); i++)
std::cout << "=";
std::cout << "\n";
{
std::cout << "----Fullstack working lists\n";
std::cout << "[0]\n";
printTaskCallList(WorkingCallList);
int i = 1;
for (GoalDecomposition& stackFrame : DecompositionStack)
{
std::cout << "[" << i++ << "]\n";
printTaskCallList(stackFrame.CallList);
}
std::cout << "----\n";
}
}
TaskCallList compoundDecompositions;
bool methodFailed = false;
for (TaskCallListIterator currentTaskCallIter = currentStackFrame.CallList.begin();
currentTaskCallIter != currentStackFrame.CallList.end();
currentTaskCallIter = currentStackFrame.CallList.erase(currentTaskCallIter))
{
TaskCall currentTaskCall = (*currentTaskCallIter);
Task* currentTask = currentTaskCall.TaskToCall;
if (!currentTask)
continue;
TaskType currentTaskType = currentTask->GetType();
if (DebugPrint)
std::cout << "TaskType currentTaskType = " << (int)currentTaskType << "\n";
switch (currentTaskType)
{
case TaskType::Goal:
{
if (DebugPrint)
std::cout << "Goal\n";
GoalTask* goalTask = currentTask->GetGoal();
// TODO erase ahead of time because fuck
// Strange things are afoot when we push to stack
currentStackFrame.CallList.erase(currentTaskCallIter);
// Perform the decomposition. This function only fails if the method at method
// index doesn't exist. This means that the goal task has no alternative options
if (!DecomposeGoalTask(DecompositionStack, goalTask, 0, currentTaskCall.Parameters,
currentStackFrame.WorkingState))
{
methodFailed = true;
break;
}
// This code is wrong. I'm not sure why.
// Pushing to the stack invalidates our currentStackFrame reference; update it
// What the actual fuck (updating the ref doesn't fix the fucking thing)
/*std::cout << "Ref updating from " << &currentStackFrame << " to "
<< &(*(DecompositionStack.end() - 1)) << "\n";
currentStackFrameIter = DecompositionStack.end() - 1;
currentStackFrame = *currentStackFrameIter;*/
return Status::Running_SuccessfulDecompositionStackPush;
}
break;
case TaskType::Primitive:
{
if (DebugPrint)
std::cout << "Primitive\n";
PrimitiveTask* primitiveTask = currentTask->GetPrimitive();
if (!primitiveTask->StateMeetsPreconditions(currentStackFrame.WorkingState,
currentTaskCall.Parameters))
{
methodFailed = true;
break;
}
primitiveTask->ApplyStateChange(currentStackFrame.WorkingState,
currentTaskCall.Parameters);
currentStackFrame.FinalCallList.push_back(currentTaskCall);
if (BreakOnPrimitiveApply)
{
currentStackFrame.CallList.erase(currentTaskCallIter);
return Status::Running_SuccessfulPrimitive;
}
// Keep processing tasks otherwise
}
break;
case TaskType::Compound:
{
if (DebugPrint)
std::cout << "Compound\n";
CompoundTask* compoundTask = currentTask->GetCompound();
if (!DecomposeCompoundTask(compoundDecompositions, compoundTask,
currentStackFrame.WorkingState,
currentTaskCall.Parameters))
{
methodFailed = true;
break;
}
currentTaskCallIter = currentStackFrame.CallList.erase(currentTaskCallIter);
// we need to push our decomposition to our call list, but we're iterating on
// it. By pushing our decomposition to compoundDecompositions, we break out of
// the loop and tack it on there
}
break;
default:
return Status::Failed_BadData;
}
if (methodFailed)
{
if (DebugPrint)
std::cout << "Method failed decomposition\n";
// Clear stack frame
currentStackFrame.CallList.clear();
currentStackFrame.FinalCallList.clear();
currentStackFrame.WorkingState = currentStackFrame.InitialState;
// Try the next method
currentStackFrame.MethodIndex++;
if (!currentStackFrame.DecomposedGoalTask->DecomposeMethodAtIndex(
currentStackFrame.CallList, currentStackFrame.MethodIndex,
currentStackFrame.Parameters))
{
DecompositionStack.pop_back();
return Status::Running_FailedGoalDecomposition;
}
// We have failed to decompose the previous method, but successfully got the next
// method. Return to let the Planner know what just happened
return Status::Running_FailedMethodDecomposition;
}
if (compoundDecompositions.size())
{
if (DebugPrint)
{
std::cout << "compoundDecompositions.size() = " << compoundDecompositions.size()
<< "\n";
std::cout << "currentStackFrame.CallList.size() = "
<< currentStackFrame.CallList.size() << "\n";
std::cout << "Decomposition:\n";
printTaskCallList(compoundDecompositions);
}
currentStackFrame.CallList.insert(currentStackFrame.CallList.begin(),
compoundDecompositions.begin(),
compoundDecompositions.end());
if (DebugPrint)
std::cout << "PlanStep Done\n";
// We have to break here because we are adding things to the list we're iterating
// on; We'll process the tasks next Step
return Status::Running_SuccessfulDecomposition;
}
if (DebugPrint)
std::cout << "Loop Done\n";
}
// Finished processing this stack frame
if (currentStackFrame.CallList.empty() && !currentStackFrame.FinalCallList.empty())
{
bool onlyStackFrame = DecompositionStack.size() == 1;
TaskCallList* parentFinalCallList = nullptr;
WorldState* parentWorkingState = nullptr;
// If this is the only frame on the stack, its call list and working state goes to the
// plan, else, the previous stack
if (onlyStackFrame)
{
parentFinalCallList = &FinalCallList;
parentWorkingState = &StacklessState;
}
else
{
GoalDecomposition& previousStackFrame = *(currentStackFrameIter - 1);
parentFinalCallList = &previousStackFrame.FinalCallList;
parentWorkingState = &previousStackFrame.WorkingState;
}
if (DebugPrint)
{
std::cout << "Collapsing stack frame. Adding List:\n";
printTaskCallList(currentStackFrame.FinalCallList);
std::cout << "To parent:\n";
printTaskCallList(*parentFinalCallList);
}
parentFinalCallList->insert(parentFinalCallList->end(),
currentStackFrame.FinalCallList.begin(),
currentStackFrame.FinalCallList.end());
*parentWorkingState = currentStackFrame.WorkingState;
DecompositionStack.pop_back();
if (DebugPrint)
std::cout << "Frame Done\n";
return Status::Running_SuccessfulDecompositionStackPop;
}
return Status::PlanComplete;
}
// TODO: Pool various task lists?
// TODO: Pull more things out into functions, if possible. It's bad that whenever I make a change to
// something I have to change it in two places
Planner::Status Planner::PlanStep(void)
{
Status status = Status::Failed_NoPossiblePlan;
// Continue stepping the plan until a non-running state is complete or a status is returned
// which matches the user-specified break conditions
do
{
if (DecompositionStack.empty())
status = PlanStep_BottomLevel();
else
status = PlanStep_StackFrame();
// The user can decided how much planning happens in a single step; conditionally stop
// stepping here based on their settings
if ((BreakOnCompoundDecomposition && status == Status::Running_SuccessfulDecomposition) ||
(BreakOnPrimitiveApply && status == Status::Running_SuccessfulPrimitive) ||
(BreakOnStackAction && (status == Status::Running_SuccessfulDecompositionStackPush ||
status == Status::Running_SuccessfulDecompositionStackPop ||
status == Status::Running_FailedGoalDecomposition)) ||
(BreakOnFailedDecomposition && (status == Status::Running_FailedGoalDecomposition ||
status == Status::Running_FailedMethodDecomposition)))
return status;
} while (status >= Status::Running_EnumBegin && status <= Status::Running_EnumEnd);
return status;
}
}

96
src/ai/htn/HTNPlanner.hpp

@ -0,0 +1,96 @@
#pragma once
#include "HTNTypes.hpp"
#include "HTNTasks.hpp"
namespace Htn
{
struct GoalDecomposition
{
// The GoalTask this decomposition came from
GoalTask* DecomposedGoalTask;
// The index to the Method used for the current decomposition
int MethodIndex;
// The tasks themselves created by the Method
TaskCallList CallList;
// The state and parameters at the time this decomposition took place
WorldState InitialState;
ParameterList Parameters;
// State while the method is being decomposed
WorldState WorkingState;
// The result of this stack frame decomposition (only primitive tasks remaining)
TaskCallList FinalCallList;
};
typedef std::vector<GoalDecomposition> GoalDecompositionStack;
// TODO: Either make planner allow making multiple plans with the same instance, or make it clear
// that it is for a single plan only
class Planner
{
public:
WorldState State;
TaskCallList InitialCallList;
// Filled out by PlanStep(); Should only be used once PlanStep returns PlanComplete
TaskCallList FinalCallList;
//
// Settings to tweak how long you want a single PlanStep() to be
//
// Break whenever a stack action has occurred (a goal has been decomposed, a method has finished
// decomposition, or a method failed to decompose)
bool BreakOnStackAction = true;
// Break immediately after a compound task has been decomposed
bool BreakOnCompoundDecomposition = false;
// Break immediately after a primitive task has been applied
bool BreakOnPrimitiveApply = false;
// Break whenever a goal or method failed to decompose
bool BreakOnFailedDecomposition = false;
// Print various details about the status of the stack etc.
bool DebugPrint = false;
enum class Status
{
// Bad
Failed_BadData = 0,
Failed_NoPossiblePlan,
Failed_NoTasks,
Running_EnumBegin,
Running_SuccessfulDecomposition,
Running_SuccessfulDecompositionStackPush,
Running_SuccessfulDecompositionStackPop,
Running_SuccessfulPrimitive,
Running_FailedMethodDecomposition,
Running_FailedGoalDecomposition,
Running_EnumEnd,
// Done
PlanComplete
};
Status PlanStep(void);
private:
GoalDecompositionStack DecompositionStack;
// Used for when all goals have been decomposed to manage mutable state
WorldState StacklessState;
// Copy of InitialCallList that Planner can fuck with
TaskCallList WorkingCallList;
Status PlanStep_StackFrame(void);
Status PlanStep_BottomLevel(void);
};
};

114
src/ai/htn/HTNTasks.cpp

@ -0,0 +1,114 @@
#include "HTNTasks.hpp"
#include <cassert>
#include <iostream>
namespace Htn
{
int GoalTask::GetNumMethods(void)
{
return Methods ? Methods->size() : 0;
}
bool GoalTask::DecomposeMethodAtIndex(TaskCallList& decomposition, int index,
const ParameterList& parameters)
{
Task* methodToDecompose = GetMethodAtIndex(index);
if (!methodToDecompose)
return false;
// TODO Fix this is screwy
decomposition.push_back({methodToDecompose, parameters});
return true;
}
Task* GoalTask::GetMethodAtIndex(int index)
{
if (index < 0 || index >= GetNumMethods() || !Methods)
return nullptr;
return (*Methods)[index];
}
void GoalTask::SetMethods(TaskList* newMethods)
{
Methods = newMethods;
}
Task::Task(GoalTask* goal)
{
Goal = goal;
Type = TaskType::Goal;
}
Task::Task(CompoundTask* compound)
{
Compound = compound;
Type = TaskType::Compound;
}
Task::Task(PrimitiveTask* primitive)
{
Primitive = primitive;
Type = TaskType::Primitive;
}
TaskType Task::GetType(void) const
{
return Type;
}
GoalTask* Task::GetGoal(void)
{
assert(Type == TaskType::Goal);
return Goal;
}
CompoundTask* Task::GetCompound(void)
{
assert(Type == TaskType::Compound);
return Compound;
}
PrimitiveTask* Task::GetPrimitive(void)
{
assert(Type == TaskType::Primitive);
return Primitive;
}
std::ostream& operator<<(std::ostream& os, const Task& task)
{
switch (task.GetType())
{
case TaskType::None:
os << "Task Type:None " << &task;
break;
case TaskType::Goal:
os << "Task Type:Goal task addr " << &task << " Goal addr " << task.Goal;
break;
case TaskType::Compound:
os << "Task Type:Compound addr " << &task << " Compound addr " << task.Compound;
break;
case TaskType::Primitive:
os << "Task Type:Primitive addr " << &task << " Primitive addr " << task.Primitive;
break;
}
return os;
}
void printTaskList(const TaskList& tasks)
{
std::cout << "TaskList size = " << tasks.size() << " addr " << &tasks << ":\n";
for (unsigned int i = 0; i < tasks.size(); i++)
{
std::cout << "\t[" << i << "] " << *tasks[i] << "\n";
}
}
void printTaskCallList(const TaskCallList& tasks)
{
std::cout << "TaskCallList size = " << tasks.size() << " addr " << &tasks << ":\n";
for (unsigned int i = 0; i < tasks.size(); i++)
{
std::cout << "\t[" << i << "] " << *tasks[i].TaskToCall << "\n";
}
}
}

107
src/ai/htn/HTNTasks.hpp

@ -0,0 +1,107 @@
#pragma once
#include "HTNTypes.hpp"
#include <iostream>
namespace Htn
{
struct Task;
typedef std::vector<Task*> TaskList;
struct TaskCall
{
Task* TaskToCall;
ParameterList Parameters;
};
typedef std::vector<TaskCall> TaskCallList;
typedef TaskCallList::iterator TaskCallListIterator;
//
// Tasks
//
class GoalTask
{
private:
TaskList* Methods;
public:
GoalTask(void) = default;
int GetNumMethods(void);
Task* GetMethodAtIndex(int index);
bool DecomposeMethodAtIndex(TaskCallList& decomposition, int index,
const ParameterList& parameters);
void SetMethods(TaskList* newMethods);
};
class CompoundTask
{
public:
CompoundTask(void) = default;
virtual ~CompoundTask(void) = default;
virtual bool StateMeetsPreconditions(const WorldState& state,
const ParameterList& parameters) const = 0;
virtual bool Decompose(TaskCallList& taskCallList, const WorldState& state,
const ParameterList& parameters) = 0;
};
class PrimitiveTask
{
public:
PrimitiveTask(void) = default;
virtual ~PrimitiveTask(void) = default;
virtual bool StateMeetsPreconditions(const WorldState& state,
const ParameterList& parameters) const = 0;
virtual void ApplyStateChange(WorldState& state, const ParameterList& parameters) = 0;
// Returns whether or not starting the task was successful (NOT whether the task completed)
// Execution should (when completed etc.) modify the world state the same as ApplyStateChange
// would. Call that function for extra safety
virtual bool Execute(WorldState& state, const ParameterList& parameters) = 0;
};
enum class TaskType
{
None = 0,
Goal,
Compound,
Primitive
};
// Instead of using inheritance, just use a simple struct which stores all types of tasks but
// only allow only one thing to be filled in for it
struct Task
{
Task(void) = delete;
Task(GoalTask* goal);
Task(CompoundTask* compound);
Task(PrimitiveTask* primitive);
TaskType GetType(void) const;
GoalTask* GetGoal(void);
CompoundTask* GetCompound(void);
PrimitiveTask* GetPrimitive(void);
friend std::ostream& operator<<(std::ostream& os, const Task& task);
private:
union
{
GoalTask* Goal;
CompoundTask* Compound;
PrimitiveTask* Primitive;
};
TaskType Type;
};
std::ostream& operator<<(std::ostream& os, const Task& task);
void printTaskList(const TaskList& tasks);
void printTaskCallList(const TaskCallList& tasks);
}

43
src/ai/htn/HTNTypes.hpp

@ -0,0 +1,43 @@
#pragma once
#include "../entityComponentSystem/EntityTypes.hpp"
namespace Htn
{
// Htn::Parameter is used as a way to send arbitrary arguments between different types of Tasks
// TODO: Add getters/setters so this is safe to use
struct Parameter
{
enum class ParamType
{
None = 0,
// Primitives
Int,
Float,
Bool,
// Game-specific
Entity
};
ParamType Type;
union
{
int IntValue;
float FloatValue;
bool BoolValue;
Entity EntityValue;
};
};
typedef std::vector<Parameter> ParameterList;
typedef std::vector<Parameter>::iterator ParameterListIterator;
typedef std::vector<Parameter>::const_iterator ParameterListConstIterator;
typedef std::vector<Parameter>::reverse_iterator ParameterListReverseIterator;
// The arguments passed to most all Task functions
typedef int WorldState; // TODO
}

BIN
src/experiments/flatbuffers/SavedHello.bin

Binary file not shown.

BIN
src/experiments/flatbuffers/testFlatbuffers_write

Binary file not shown.

95
src/objectComponent/Component.cpp

@ -1,95 +0,0 @@
#ifndef COMPONENT_CPP
#define COMPONENT_CPP
#include <iostream>
#include "Component.hpp"
#include "ObjectType.hpp"
#include "Object.hpp"
Component::Component()
{
}
Component::~Component()
{
}
ComponentType Component::getType()
{
return type;
}
ObjectType Component::getOwnerType()
{
return ownerType;
}
ObjectID Component::getOwnerID()
{
return ownerID;
}
// This function should NEVER return NULL on a valid initialized Component.
// This function is used by ComponentManagers when moving Components in memory
// Make SURE you set your parentObject pointer in initialize()!
Object* Component::getParentObject()
{
return parentObject;
}
// Initialize the component. Components should be able to be reused after initialize is called.
// Components should store their parent object's address for later use
// Return false if initialization failed for any reason, and the object creation will
// be aborted
bool Component::initialize(Object* newParentObject)
{
return false;
}
// Once all sibling components in an object have been initialize, postInitialize is executed.
// Use this function to find all sibling components that your component is dependent on, as well
// as any component-component initialization communication that might need to happen.
bool Component::postInitialize()
{
return true;
}
// The parent Object has been scheduled to be destroyed. preDestroy() is called on every component
// before components are actually destroyed. This is the "last supper" for components. Use
// this time to do any component-component destruction communication
void Component::preDestroy()
{
}
// The parent Object is being destroyed. Perform any destruction necessary here. It is up to
// you to decide what is destroyed in destroy() and what is cleared in initialize().
void Component::destroy()
{
}
// Perform all logic for your component here
void Component::update(ComponentUpdateParams& updateParams)
{
}
// Output or render as much helpful information as you can about your component in this
// function.
void Component::debugPrint()
{
std::cout << "WARNING: Undefined debugPrint for component of type " << (int)type << "\n";
}
// Copy all data needed to make an exact replica of the current component to the
// new component. This function will be used when an entire Object or Component is moved.
// Return false if there was an error copying (or it is impossible to copy)
// The new component has already been initialized, but not postInitialized (should it be?)
// You should NOT set the current Component's parentObject pointer to the newComponent's
bool Component::copyComponent(Object* newParentObject, Component* newComponent)
{
return false;
}
#endif

89
src/objectComponent/Component.hpp

@ -1,89 +0,0 @@
#ifndef COMPONENT_HPP
#define COMPONENT_HPP
#include "ObjectID.hpp"
#include "ObjectType.hpp"
/* --Component--
* Components are modular bits of logic and data designed to work with other
* modular components. Objects are collections of components. Component is an abstract
* class.
*/
enum ComponentType;
struct ComponentUpdateParams;
class Object;
class ComponentManager;
class Component
{
public:
Component();
// Note that the constructor and destructor shouldn't have to be executed
// every time a Component is created. Initialize and Deinitalize should do everything
// necessary for a component to be reused
virtual ~Component();
// Getters for types
ComponentType getType();
ObjectType getOwnerType();
ObjectID getOwnerID();
// This function should NEVER return NULL on a valid initialized Component.
// This function is used by ComponentManagers when moving Components in memory
// Make SURE you set your parentObject pointer in initialize()!
Object* getParentObject();
// Initialize the component. Components should be able to be reused after initialize is called.
// Components should store their parent object's address for later use
// Return false if initialization failed for any reason, and the object creation will
// be aborted
virtual bool initialize(Object* newParentObject);
// Once all sibling components in an object have been initialize, postInitialize is executed.
// Use this function to find all sibling components that your component is dependent on, as well
// as any component-component initialization communication that might need to happen.
virtual bool postInitialize();
// The parent Object has been scheduled to be destroyed. preDestroy() is called on every component
// before components are actually destroyed. This is the "last supper" for components. Use
// this time to do any component-component destruction communication
virtual void preDestroy();
// The parent Object is being destroyed. Perform any destruction necessary here. It is up to
// you to decide what is destroyed in destroy() and what is cleared in initialize().
virtual void destroy();
// Perform all logic for your component here
virtual void update(ComponentUpdateParams& updateParams);
// Output or render as much helpful information as you can about your component in this
// function.
virtual void debugPrint();
// Copy all data needed to make an exact replica of the current component to the
// new component. This function will be used when an entire Object or Component is moved.
// Return false if there was an error copying (or it is impossible to copy)
// The new component has already been initialized, but not postInitialized (should it be?)
// You should NOT set the current Component's parentObject pointer to the newComponent's
virtual bool copyComponent(Object* newParentObject, Component* newComponent);
// TODO: Component save and load functions
//virtual bool save(SaveBuffer *saveBuffer);
//virtual bool load(LoadBuffer *loadBuffer);
// This value can be used by the ComponentManager to find a component
// in a Pool, array, list, etc
int localID;
protected:
// The parent object of this component. This value should always be
// valid and set as the ComponentManager will use it
Object* parentObject;
ComponentType type;
private:
ObjectType ownerType;
ObjectID ownerID;
};
#endif // COMPONENT_HPP

54
src/objectComponent/ComponentManager.cpp

@ -1,54 +0,0 @@
#ifndef COMPONENTMANAGER_CPP
#define COMPONENTMANAGER_CPP
#include "ComponentManager.hpp"
ComponentType ComponentManager::getType()
{
return type;
}
ComponentManager::ComponentManager()
{
}
ComponentManager::~ComponentManager()
{
}
// Initialize the ComponentManager. The manager should work without
// the constructor having been run before
bool ComponentManager::initialize()
{
return true;
}
// Do any destruction necessary for this manager.
void ComponentManager::destroy()
{
}
// Update all of the active Components, passing them the ComponentUpdateParams
void ComponentManager::updateAllComponents(ComponentUpdateParams& componentUpdateParams)
{
}
// Return a pointer to a fresh Component. Do not run initialize() or
// anything on the component - this function's caller should handle that.
// Return NULL if the request cannot be completed (e.g., the pool is full)
Component* ComponentManager::getNewRawComponent()
{
return nullptr;
}
// Run destroy() on the Component and remove the component from this manager's memory.
void ComponentManager::destroyComponent(Component* component)
{
}
#endif

46
src/objectComponent/ComponentManager.hpp

@ -1,46 +0,0 @@
#ifndef COMPONENTMANAGER_HPP
#define COMPONENTMANAGER_HPP
#include "ComponentTypes.hpp"
/* --ComponentManager--
* ComponentManagers handle the storage, creation, and updating of a single
* type of Component. ComponentManager is a polymorphic class so that
* unique behaviors can be determined for all of these functions.
* */
class Object;
class Component;
struct ComponentUpdateParams;
class ComponentManager
{
public:
ComponentManager();
virtual ~ComponentManager();
ComponentType getType();
// Initialize the ComponentManager. The manager should work without
// the constructor having been run before
virtual bool initialize();
// Do any destruction necessary for this manager.
virtual void destroy();
// Update all of the active Components, passing them the ComponentUpdateParams
virtual void updateAllComponents(ComponentUpdateParams& componentUpdateParams);
// Return a pointer to a fresh Component. Do not run initialize() or
// anything on the component - this function's caller should handle that.
// Return NULL if the request cannot be completed (e.g., the pool is full)
virtual Component* getNewRawComponent();
// Run destroy() on the Component and remove the component from this manager's memory.
virtual void destroyComponent(Component* component);
private:
ComponentType type;
};
#endif

10
src/objectComponent/ComponentTypes.hpp

@ -1,10 +0,0 @@
#ifndef COMPONENTTYPES_HPP
#define COMPONENTTYPES_HPP
enum class ComponentType : int
{
NONE = 0,
TEST
};
#endif

169
src/objectComponent/Object.cpp

@ -1,169 +0,0 @@
#ifndef OBJECT_CPP
#define OBJECT_CPP
#include <assert.h>
#include "Object.hpp"
#include "Component.hpp"
const int COMPONENTHANDLE_NULL = -1;
Object::Object()
{
initialize();
}
Object::~Object()
{
}
void Object::initialize()
{
shouldDestroy = false;
numActiveComponents = 0;
id = OBJECT_ID_NONE;
type = nullptr;
}
ObjectID Object::getObjectID()
{
return id;
}
// Provides a layer of abstraction around the internal storage of Component pointers
Component* Object::getComponentAtIndex(int i)
{
if (i >= 0 && i < numActiveComponents)
return components[i];
return nullptr;
}
// Searches through components this object is composed of for one of
// the provided type. Returns a handle
// Returns a ComponentHandle which can be used to get a pointer to
// a component of the requested type.
Object::ComponentHandle Object::getComponentHandleForType(ComponentType componentType)
{
for (int i = 0; i < numActiveComponents; i++)
{
Component* currentComponent = getComponentAtIndex(i);
if (currentComponent)
{
if (currentComponent->getType() == componentType)
return i;
}
}
return COMPONENTHANDLE_NULL;
}
// Returns true if the ComponentHandle is valid, and points to a valid
// Component.
bool Object::isValidComponentHandle(ComponentHandle componentHandle)
{
if (componentHandle == Object::COMPONENTHANDLE_NULL
|| componentHandle >= numActiveComponents)
return false;
return true;
}
// Returns a pointer to the component indicated by the handle.
// This pointer should NOT be stored on the heap! Components can
// move around in memory, so storing direct pointers is not ok!
Component* Object::getComponent(Object::ComponentHandle componentHandle)
{
if (isValidComponentHandle(componentHandle))
return getComponentAtIndex(componentHandle);
return nullptr;
}
// Returns the value of shouldDestroy. If true, whatever is managing
// the object should notify all components of the impending destruction,
// destroy the components, then destroy the object
bool Object::shouldDestroyObject()
{
return shouldDestroy;
}
// Sets the value of shouldDestroy to true. Once set, it cannot be
// undone. The actual destruction should be handled by the object's
// manager after checking shouldDestroyObject()
// This function is meant to be used by the object's components to self-destruct
// Destruction will not happen immediately - it is up to the object's manager
void Object::requestObjectDestruction()
{
shouldDestroy = true;
}
void Object::setObjectID(ObjectID newObjectID)
{
id = newObjectID;
}
void Object::setObjectType(ObjectType* newObjectType)
{
type = newObjectType;
}
void Object::addComponent(Component* newComponent)
{
if (numActiveComponents < OBJECT_MAX_COMPONENTS)
{
components[numActiveComponents] = newComponent;
numActiveComponents++;
}
else
{
// Tried to add more than OBJECT_MAX_COMPONENTS
assert(0);
}
}
int Object::getNumActiveComponents()
{
return numActiveComponents;
}
// Run postInitialize on all components
bool Object::postInitializeComponents()
{
// For each active component
for (int i = 0; i < numActiveComponents; i++)
{
Component* currentComponent = getComponentAtIndex(i);
if (currentComponent)
{
// Run postInitialize on the component
if (!currentComponent->postInitialize())
return false;
}
}
return true;
}
// Run preDestroy on all components
void Object::preDestroyComponents()
{
// For each active component
for (int i = 0; i < numActiveComponents; i++)
{
Component* currentComponent = getComponentAtIndex(i);
if (currentComponent)
{
currentComponent->preDestroy();
}
}
}
// Resets components[] by changing numActiveComponents to 0; this is
// only used if a partially initialized Object's component failed to initialize
void Object::resetComponentsArray()
{
numActiveComponents = 0;
}
#endif

113
src/objectComponent/Object.hpp

@ -1,113 +0,0 @@
#ifndef OBJECT_HPP
#define OBJECT_HPP
#include "ObjectID.hpp"
#include "ObjectType.hpp"
#include "ComponentTypes.hpp"
/* --Object--
* Objects hold a collection of Components. Object provides an abstraction
* layer for Components so that components can be moved in memory without
* affecting other Components.
*
* Note that Objects cannot hold more than one Component of the same type.
* If this is required for some reason, you should redesign the system, or
* make the component support it internally
*/
class Component;
struct ObjectType;
class ObjectComponentManager;
class Object
{
public:
Object();
~Object();
// Initialize the object
// Objects should work completely after initialize, even if the constructor isn't called
void initialize();
ObjectID getObjectID();
// A handle to a Component this Object has
// Components (and all other external users of an Object's components) should
// NEVER store pointers to an Object's components on anything other than
// the stack. ComponentHandles, however, are safe to store. This is so
// that Components can be moved around in memory without breaking references
typedef int ComponentHandle;
// Searches through components this object is composed of for one of
// the provided type.
// Returns a ComponentHandle which can be used to get a pointer to
// a component of the requested type.
// Note that Objects can only have one Component of the same type
// Check your ComponentHandle's validity with isValidComponentHandle()
ComponentHandle getComponentHandleForType(ComponentType componentType);
// Returns true if the ComponentHandle is valid, and points to a valid
// Component.
bool isValidComponentHandle(ComponentHandle componentHandle);
// Returns a pointer to the component indicated by the handle.
// This pointer should NOT be stored on the heap! Components can
// move around in memory, so storing direct pointers is not ok!
// Note that getComponent() checks the validity of the ComponentHandle
// Returns nullptr if the Handle is invalid
Component* getComponent(ComponentHandle componentHandle);
// Returns the value of shouldDestroy. If true, whatever is managing
// the object should notify all components of the impending destruction,
// destroy the components, then destroy the object
bool shouldDestroyObject();
// Sets the value of shouldDestroy to true. Once set, it cannot be
// undone. The actual destruction should be handled by the object's
// manager after checking shouldDestroyObject()
// This function is meant to be used by the object's components to self-destruct
// Destruction will not happen immediately - it is up to the object's manager
void requestObjectDestruction();
friend class ObjectComponentManager;
protected:
// Functions for ObjectComponentManager
void setObjectID(ObjectID newObjectID);
void setObjectType(ObjectType* newObjectType);
// Add a new, initialized component to this object
void addComponent(Component* newComponent);
// Run postInitialize on all components
bool postInitializeComponents();
// Run preDestroy on all components
void preDestroyComponents();
int getNumActiveComponents();
// Provides a layer of abstraction around the internal storage of Component pointers
Component* getComponentAtIndex(int i);
// Resets components[] by changing numActiveComponents to 0; this is
// only used if a partially initialized Object's component failed to initialize
void resetComponentsArray();
private:
// A list of pointers to Components the Object currently has (up to numActiveComponents)
// OBJECT_MAX_COMPONENTS is defined in ObjectType.hpp
Component* components[OBJECT_MAX_COMPONENTS];
int numActiveComponents;
ObjectType* type;
ObjectID id;
// shouldDestroy indicates whether or not the Object should be
// destroyed. This is meant to be used by Components to self-destruct.
// shouldDestroy should be checked by whatever is managing
// the object after the object's components have been updated
bool shouldDestroy;
// The NULL value for ComponentHandles
static const int COMPONENTHANDLE_NULL = -1;
};
#endif

381
src/objectComponent/ObjectComponentManager.cpp

@ -1,381 +0,0 @@
#ifndef OBJECTCOMPONENTMANAGER_CPP
#define OBJECTCOMPONENTMANAGER_CPP
#include <assert.h>
#include <iostream>
#include "ObjectComponentManager.hpp"
#include "ComponentManager.hpp"
#include "Component.hpp"
ObjectComponentManager::ObjectComponentManager():objectPool(ObjectComponentManager::OBJECT_POOL_SIZE)
{
}
ObjectComponentManager::~ObjectComponentManager()
{
destroyAllActiveObjects();
}