diff --git a/Engine/source/BadBehavior/composite/ActiveSelector.cpp b/Engine/source/BadBehavior/composite/ActiveSelector.cpp new file mode 100644 index 0000000000..05d3d855ff --- /dev/null +++ b/Engine/source/BadBehavior/composite/ActiveSelector.cpp @@ -0,0 +1,130 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2014 Guy Allard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//----------------------------------------------------------------------------- + +#include "ActiveSelector.h" + +using namespace BadBehavior; + +//------------------------------------------------------------------------------ +// Active selector node +//------------------------------------------------------------------------------ +IMPLEMENT_CONOBJECT(ActiveSelector); + +ActiveSelector::ActiveSelector() + : mRecheckFrequency(0) +{ +} + +Task *ActiveSelector::createTask(SimObject &owner, BehaviorTreeRunner &runner) +{ + return new ActiveSelectorTask(*this, owner, runner); +} + +void ActiveSelector::initPersistFields() +{ + addGroup( "Behavior" ); + + addField( "recheckFrequency", TypeS32, Offset(mRecheckFrequency, ActiveSelector), + "@brief The minimum time period in milliseconds to wait between re-evaluations of higher priority branches."); + + endGroup( "Behavior" ); + + Parent::initPersistFields(); +} + +//------------------------------------------------------------------------------ +// Active selector task +//------------------------------------------------------------------------------ +ActiveSelectorTask::ActiveSelectorTask(Node &node, SimObject &owner, BehaviorTreeRunner &runner) + : Parent(node, owner, runner), + mRecheckTime(0) +{ +} + +void ActiveSelectorTask::onInitialize() +{ + Parent::onInitialize(); + + if(mBranches.empty()) + { + for (VectorPtr::iterator i = mChildren.begin(); i != mChildren.end(); ++i) + { + mBranches.push_back(BehaviorTreeBranch(*i)); + } + } + + mCurrentBranch = mBranches.begin(); + mRunningBranch = mBranches.end(); + mRecheckTime = 0; +} + + +Task* ActiveSelectorTask::update() +{ + // empty node, bail + if(mBranches.empty()) + { + mStatus = INVALID; + return NULL; + } + + // is it time to re-check higher priority branches? + if(Sim::getCurrentTime() >= mRecheckTime) + { + // pick highest priority branch + mCurrentBranch = mBranches.begin(); + + // determine the next recheck time + mRecheckTime = Sim::getCurrentTime() + static_cast(mNodeRep)->getRecheckFrequency(); + } + + // run a branch, if it fails move on to the next + for(mCurrentBranch; mCurrentBranch != mBranches.end(); ++mCurrentBranch) + { + // reset the branch if it's not the current running branch + if(mCurrentBranch != mRunningBranch) + mCurrentBranch->reset(); + + mStatus = mCurrentBranch->update(); + + if(mStatus == FAILURE) // move on to next + continue; + + if(mStatus == RUNNING || mStatus == SUSPENDED) // track the current running branch + mRunningBranch = mCurrentBranch; + + break; + } + + if( (mStatus != RUNNING && mStatus != SUSPENDED) || mCurrentBranch == mBranches.end() ) + mIsComplete = true; + + return NULL; +} + +Status ActiveSelectorTask::getStatus() +{ + if(mStatus == SUSPENDED && mCurrentBranch != mBranches.end()) + return mCurrentBranch->getStatus(); // suspended branch may have resumed + + return mStatus; +} diff --git a/Engine/source/BadBehavior/composite/ActiveSelector.h b/Engine/source/BadBehavior/composite/ActiveSelector.h new file mode 100644 index 0000000000..939979f38c --- /dev/null +++ b/Engine/source/BadBehavior/composite/ActiveSelector.h @@ -0,0 +1,92 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2014 Guy Allard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//----------------------------------------------------------------------------- + +#ifndef _BB_ACTIVESELECTOR_H_ +#define _BB_ACTIVESELECTOR_H_ + +#ifndef _BB_CORE_H_ +#include "BadBehavior/core/Composite.h" +#endif +#ifndef _BB_BRANCH_H_ +#include "BadBehavior/core/Branch.h" +#endif + +//============================================================================== +// Active Selector +// Re-evaluates its children from the beginning each tick. Lower priority +// children which previously returned RUNNING are resumed if re-selected +// +// ***** TODO - This runs OK, but may need a bit more work +// -- abort previously running branches? +// +//============================================================================== + +namespace BadBehavior +{ + //--------------------------------------------------------------------------- + // Active selector Node + //--------------------------------------------------------------------------- + class ActiveSelector : public CompositeNode + { + typedef CompositeNode Parent; + + protected: + U32 mRecheckFrequency; + + public: + ActiveSelector(); + + virtual Task *createTask(SimObject &owner, BehaviorTreeRunner &runner); + + static void initPersistFields(); + + U32 getRecheckFrequency() const { return mRecheckFrequency; } + + DECLARE_CONOBJECT(ActiveSelector); + }; + + //--------------------------------------------------------------------------- + // Active selector Task + //--------------------------------------------------------------------------- + class ActiveSelectorTask : public CompositeTask + { + typedef CompositeTask Parent; + + protected: + Vector::iterator mRunningBranch; + Vector::iterator mCurrentBranch; + Vector mBranches; + + U32 mRecheckTime; + + virtual void onInitialize(); + virtual Task* update(); + + public: + ActiveSelectorTask(Node &node, SimObject &owner, BehaviorTreeRunner &runner); + + Status getStatus(); +}; + +} // namespace BadBehavior + +#endif \ No newline at end of file diff --git a/Engine/source/BadBehavior/composite/Parallel.cpp b/Engine/source/BadBehavior/composite/Parallel.cpp new file mode 100644 index 0000000000..85eecff172 --- /dev/null +++ b/Engine/source/BadBehavior/composite/Parallel.cpp @@ -0,0 +1,173 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2014 Guy Allard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//----------------------------------------------------------------------------- + +#include "Parallel.h" + +using namespace BadBehavior; + +//------------------------------------------------------------------------------ +// Parallel node +//------------------------------------------------------------------------------ +IMPLEMENT_CONOBJECT(Parallel); + +Parallel::Parallel() + : mReturnPolicy(REQUIRE_ALL) +{ +} + +ImplementEnumType( ParallelReturnPolicy, + "@brief The policy to use when determining the return status of a parallel node.\n\n" + "@ingroup AI\n\n") + { Parallel::REQUIRE_ALL, "REQUIRE_ALL", "Will return success if all children succeed.\n" + "Will terminate and return failure if one child fails \n"}, + { Parallel::REQUIRE_NONE, "REQUIRE_NONE", "Will return success even if all children fail.\n" }, + { Parallel::REQUIRE_ONE, "REQUIRE_ONE", "Will terminate and return success when one child succeeds.\n" }, +EndImplementEnumType; + +void Parallel::initPersistFields() +{ + addGroup( "Behavior" ); + + addField( "returnPolicy", TYPEID< Parallel::ParallelPolicy >(), Offset(mReturnPolicy, Parallel), + "@brief The policy to use when deciding the return status for the parallel sequence."); + + endGroup( "Behavior" ); + + Parent::initPersistFields(); +} + +Task *Parallel::createTask(SimObject &owner, BehaviorTreeRunner &runner) +{ + return new ParallelTask(*this, owner, runner); +} + +//------------------------------------------------------------------------------ +// Parallel Task +//------------------------------------------------------------------------------ +ParallelTask::ParallelTask(Node &node, SimObject &owner, BehaviorTreeRunner &runner) + : Parent(node, owner, runner), + mHasSuccess(false), + mHasFailure(false) +{ +} + +void ParallelTask::onInitialize() +{ + Parent::onInitialize(); + + mHasSuccess = mHasFailure = false; + + if(mBranches.empty()) + { + for (VectorPtr::iterator i = mChildren.begin(); i != mChildren.end(); ++i) + { + mBranches.push_back(BehaviorTreeBranch(*i)); + } + } + else + { + for (Vector::iterator it = mBranches.begin(); it != mBranches.end(); ++it) + { + it->reset(); + } + } +} + +Task* ParallelTask::update() +{ + bool hasRunning = false, hasSuspended = false, hasResume = false; + for (Vector::iterator it = mBranches.begin(); it != mBranches.end(); ++it) + { + Status s = it->getStatus(); + if(s == INVALID || s == RUNNING || s == RESUME) + { + s = it->update(); + + switch(it->getStatus()) + { + case SUCCESS: + mHasSuccess = true; + break; + case FAILURE: + mHasFailure = true; + break; + case RUNNING: + hasRunning = true; + break; + case SUSPENDED: + hasSuspended = true; + break; + case RESUME: + hasResume = true; + break; + } + } + } + + switch(static_cast(mNodeRep)->getReturnPolicy()) + { + // REQUIRE_NONE + // returns SUCCESS when all children have finished irrespective of their return status. + case Parallel::REQUIRE_NONE: + mStatus = hasResume ? RESUME : ( hasRunning ? RUNNING : ( hasSuspended ? SUSPENDED : SUCCESS ) ); + break; + + // REQUIRE_ONE + // terminates and returns SUCCESS when any of its children succeed + // returns FAILURE if no children succeed + case Parallel::REQUIRE_ONE: + mStatus = mHasSuccess ? SUCCESS : ( hasResume ? RESUME : ( hasRunning ? RUNNING : ( hasSuspended ? SUSPENDED : FAILURE ) ) ); + break; + + // REQUIRE_ALL + // returns SUCCESS if all of its children succeed. + // terminates and returns failure if any of its children fail + case Parallel::REQUIRE_ALL: + mStatus = mHasFailure ? FAILURE : ( hasResume ? RESUME : ( hasRunning ? RUNNING : ( hasSuspended ? SUSPENDED : SUCCESS ) ) ); + break; + } + + mIsComplete = (mStatus != RUNNING && mStatus != SUSPENDED && mStatus != RESUME); + + return NULL; +} + + +Status ParallelTask::getStatus() +{ + if(mStatus == SUSPENDED) + { + // need to check if the parallel is still suspended. + // A parallel will only report SUSPENDED when all of its children are suspended + for(Vector::iterator it = mBranches.begin(); it != mBranches.end(); ++it) + { + switch(it->getStatus()) + { + case RUNNING: + return RUNNING; + case RESUME: + return RESUME; + } + } + } + return mStatus; +} \ No newline at end of file diff --git a/Engine/source/BadBehavior/composite/Parallel.h b/Engine/source/BadBehavior/composite/Parallel.h new file mode 100644 index 0000000000..91467b44b4 --- /dev/null +++ b/Engine/source/BadBehavior/composite/Parallel.h @@ -0,0 +1,97 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2014 Guy Allard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//----------------------------------------------------------------------------- + +#ifndef _BB_PARALLEL_H_ +#define _BB_PARALLEL_H_ + +#ifndef _BB_CORE_H_ +#include "BadBehavior/core/Composite.h" +#endif + +#ifndef _BB_BRANCH_H_ +#include "BadBehavior/core/Branch.h" +#endif + +namespace BadBehavior +{ + //--------------------------------------------------------------------------- + // Parallel sequence node + // Runs all of its children irrespective of their return status + // The final return status depends on the chosen policy + // (not a true parallel, as branches are actually evaluated sequentially) + //--------------------------------------------------------------------------- + class Parallel: public CompositeNode + { + typedef CompositeNode Parent; + + public: + // parallel return policies + enum ParallelPolicy + { + REQUIRE_NONE, + REQUIRE_ONE, + REQUIRE_ALL, + }; + + protected: + ParallelPolicy mReturnPolicy; + + public: + Parallel(); + + virtual Task *createTask(SimObject &owner, BehaviorTreeRunner &runner); + + static void initPersistFields(); + + ParallelPolicy getReturnPolicy() const { return mReturnPolicy; } + + DECLARE_CONOBJECT(Parallel); + }; + + //--------------------------------------------------------------------------- + // Parallel Task + //--------------------------------------------------------------------------- + class ParallelTask: public CompositeTask + { + typedef CompositeTask Parent; + + protected: + Vector mBranches; + + bool mHasSuccess, mHasFailure; + + virtual void onInitialize(); + virtual Task* update(); + + public: + ParallelTask(Node &node, SimObject &owner, BehaviorTreeRunner &runner); + + virtual Status getStatus(); + }; + +} // namespace BadBehavior + +// make the return policy enum accessible from script +typedef BadBehavior::Parallel::ParallelPolicy ParallelReturnPolicy; +DefineEnumType( ParallelReturnPolicy ); + +#endif \ No newline at end of file diff --git a/Engine/source/BadBehavior/composite/RandomSelector.cpp b/Engine/source/BadBehavior/composite/RandomSelector.cpp new file mode 100644 index 0000000000..03e0225264 --- /dev/null +++ b/Engine/source/BadBehavior/composite/RandomSelector.cpp @@ -0,0 +1,68 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2014 Guy Allard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//----------------------------------------------------------------------------- + +#include "math/mMathFn.h" + +#include "RandomSelector.h" + +using namespace BadBehavior; + +//------------------------------------------------------------------------------ +// Random selector node +//------------------------------------------------------------------------------ +IMPLEMENT_CONOBJECT(RandomSelector); + +Task *RandomSelector::createTask(SimObject &owner, BehaviorTreeRunner &runner) +{ + return new RandomSelectorTask(*this, owner, runner); +} + +//------------------------------------------------------------------------------ +// Random selector task +//------------------------------------------------------------------------------ +RandomSelectorTask::RandomSelectorTask(Node &node, SimObject &owner, BehaviorTreeRunner &runner) + : Parent(node, owner, runner) +{ +} + +void RandomSelectorTask::onInitialize() +{ + Parent::onInitialize(); + + // randomize the order of our child tasks + VectorPtr randomChildren; + + while(mChildren.size() > 0) + { + U32 index = mRandI(0, mChildren.size() - 1); + Task* child = mChildren[index]; + randomChildren.push_back(child); + mChildren.erase_fast(index); + } + + mChildren = randomChildren; + + // normal init + mCurrentChild = mChildren.begin(); + if(mCurrentChild != mChildren.end()) + (*mCurrentChild)->reset(); +} diff --git a/Engine/source/BadBehavior/composite/RandomSelector.h b/Engine/source/BadBehavior/composite/RandomSelector.h new file mode 100644 index 0000000000..ed24177319 --- /dev/null +++ b/Engine/source/BadBehavior/composite/RandomSelector.h @@ -0,0 +1,62 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2014 Guy Allard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//----------------------------------------------------------------------------- + +#ifndef _BB_RANDOMSELECTOR_H_ +#define _BB_RANDOMSELECTOR_H_ + +#ifndef _BB_SELECTOR_H_ +#include "Selector.h" +#endif + +namespace BadBehavior +{ + //--------------------------------------------------------------------------- + // Random selector node + // selects its children in a random order until one of them succeeds + //--------------------------------------------------------------------------- + class RandomSelector : public Selector + { + typedef Selector Parent; + + public: + virtual Task *createTask(SimObject &owner, BehaviorTreeRunner &runner); + + DECLARE_CONOBJECT(RandomSelector); + }; + + //--------------------------------------------------------------------------- + // Random selector task + //--------------------------------------------------------------------------- + class RandomSelectorTask : public SelectorTask + { + typedef SelectorTask Parent; + + protected: + virtual void onInitialize(); + + public: + RandomSelectorTask(Node &node, SimObject &owner, BehaviorTreeRunner &runner); + }; + +} // namespace BadBehavior + +#endif \ No newline at end of file diff --git a/Engine/source/BadBehavior/composite/Selector.cpp b/Engine/source/BadBehavior/composite/Selector.cpp new file mode 100644 index 0000000000..b62828b5cd --- /dev/null +++ b/Engine/source/BadBehavior/composite/Selector.cpp @@ -0,0 +1,54 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2014 Guy Allard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//----------------------------------------------------------------------------- + +#include "Selector.h" + +using namespace BadBehavior; + +//------------------------------------------------------------------------------ +// Selector node +//------------------------------------------------------------------------------ +IMPLEMENT_CONOBJECT(Selector); + +Task *Selector::createTask(SimObject &owner, BehaviorTreeRunner &runner) +{ + return new SelectorTask(*this, owner, runner); +} + +//------------------------------------------------------------------------------ +// Selector task +//------------------------------------------------------------------------------ +SelectorTask::SelectorTask(Node &node, SimObject &owner, BehaviorTreeRunner &runner) + : Parent(node, owner, runner) +{ +} + +void SelectorTask::onChildComplete(Status s) +{ + mStatus = s; + + // if child failed, move on to the next child + if(mStatus == FAILURE) + ++mCurrentChild; + else + mIsComplete = true; +} diff --git a/Engine/source/BadBehavior/composite/Selector.h b/Engine/source/BadBehavior/composite/Selector.h new file mode 100644 index 0000000000..1a2386c331 --- /dev/null +++ b/Engine/source/BadBehavior/composite/Selector.h @@ -0,0 +1,60 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2014 Guy Allard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//----------------------------------------------------------------------------- + +#ifndef _BB_SELECTOR_H_ +#define _BBSELECTOR_H_ + +#ifndef _BB_CORE_H_ +#include "BadBehavior/core/Composite.h" +#endif + +namespace BadBehavior +{ + //--------------------------------------------------------------------------- + // Selector Node + //--------------------------------------------------------------------------- + class Selector : public CompositeNode + { + typedef CompositeNode Parent; + + public: + virtual Task *createTask(SimObject &owner, BehaviorTreeRunner &runner); + + DECLARE_CONOBJECT(Selector); + }; + + //--------------------------------------------------------------------------- + // Selector Task + //--------------------------------------------------------------------------- + class SelectorTask : public CompositeTask + { + typedef CompositeTask Parent; + + public: + SelectorTask(Node &node, SimObject &owner, BehaviorTreeRunner &runner); + + virtual void onChildComplete(Status); + }; + +} // namespace BadBehavior + +#endif \ No newline at end of file diff --git a/Engine/source/BadBehavior/composite/Sequence.cpp b/Engine/source/BadBehavior/composite/Sequence.cpp new file mode 100644 index 0000000000..27b87f8bd0 --- /dev/null +++ b/Engine/source/BadBehavior/composite/Sequence.cpp @@ -0,0 +1,55 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2014 Guy Allard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//----------------------------------------------------------------------------- + +#include "Sequence.h" + +using namespace BadBehavior; + +//------------------------------------------------------------------------------ +// Sequence node +//------------------------------------------------------------------------------ +IMPLEMENT_CONOBJECT(Sequence); + +Task *Sequence::createTask(SimObject &owner, BehaviorTreeRunner &runner) +{ + return new SequenceTask(*this, owner, runner); +} + +//------------------------------------------------------------------------------ +// Sequence Task +//------------------------------------------------------------------------------ +SequenceTask::SequenceTask(Node &node, SimObject &owner, BehaviorTreeRunner &runner) + : Parent(node, owner, runner) +{ +} + +void SequenceTask::onChildComplete(Status s) +{ + mStatus = s; + + // if child succeeded, move on to the next child + if(mStatus == SUCCESS) + ++mCurrentChild; + else + mIsComplete = true; +} + diff --git a/Engine/source/BadBehavior/composite/Sequence.h b/Engine/source/BadBehavior/composite/Sequence.h new file mode 100644 index 0000000000..49f07b5d15 --- /dev/null +++ b/Engine/source/BadBehavior/composite/Sequence.h @@ -0,0 +1,60 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2014 Guy Allard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//----------------------------------------------------------------------------- + +#ifndef _BB_SEQUENCE_H_ +#define _BB_SEQUENCE_H_ + +#ifndef _BB_CORE_H_ +#include "BadBehavior/core/Composite.h" +#endif + +namespace BadBehavior +{ + //--------------------------------------------------------------------------- + // Sequence Node + //--------------------------------------------------------------------------- + class Sequence : public CompositeNode + { + typedef CompositeNode Parent; + + public: + virtual Task* createTask(SimObject &owner, BehaviorTreeRunner &runner); + + DECLARE_CONOBJECT(Sequence); + }; + + //--------------------------------------------------------------------------- + // Sequence Task + //--------------------------------------------------------------------------- + class SequenceTask : public CompositeTask + { + typedef CompositeTask Parent; + + public: + SequenceTask(Node &node, SimObject &owner, BehaviorTreeRunner &runner); + + virtual void onChildComplete(Status); + }; + +} // namespace BadBehavior + +#endif \ No newline at end of file diff --git a/Engine/source/BadBehavior/core/Branch.cpp b/Engine/source/BadBehavior/core/Branch.cpp new file mode 100644 index 0000000000..69e6701144 --- /dev/null +++ b/Engine/source/BadBehavior/core/Branch.cpp @@ -0,0 +1,66 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2014 Guy Allard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//----------------------------------------------------------------------------- + +#include "Stepper.h" +#include "Branch.h" + +using namespace BadBehavior; + +BehaviorTreeBranch::BehaviorTreeBranch() + : mRootTask(NULL) +{ +} + +BehaviorTreeBranch::BehaviorTreeBranch(Task *root) + : mRootTask(root), + mStatus(INVALID) +{ +} + +Status BehaviorTreeBranch::getStatus() +{ + if(!mTasks.empty()) + return mTasks.back()->getStatus(); + + return mStatus; +} + +Status BehaviorTreeBranch::update() +{ + if(mRootTask) + { + if(mTasks.empty()) + { + mRootTask->setup(); + mTasks.push_back(mRootTask); + } + } + mStatus = BehaviorTreeStepper::stepThrough(mTasks); + return mStatus; +} + +void BehaviorTreeBranch::reset() +{ + mStatus = INVALID; + mRootTask->reset(); + mTasks.clear(); +} diff --git a/Engine/source/BadBehavior/core/Branch.h b/Engine/source/BadBehavior/core/Branch.h new file mode 100644 index 0000000000..4198b524ea --- /dev/null +++ b/Engine/source/BadBehavior/core/Branch.h @@ -0,0 +1,53 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2014 Guy Allard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//----------------------------------------------------------------------------- + +#ifndef _BB_BRANCH_H_ +#define _BB_BRANCH_H_ + +#ifndef _BB_CORE_H_ +#include "Core.h" +#endif + +namespace BadBehavior +{ + // The branch class handles a single execution path in the tree + // Typically used for the tree root and for handling suspension and concurrency + class BehaviorTreeBranch + { + private: + Status mStatus; + Task *mRootTask; + VectorPtr mTasks; + + public: + BehaviorTreeBranch(); + BehaviorTreeBranch(Task *root); + + Status getStatus(); + Status update(); + void reset(); + }; + + +} // namespace BadBehavior + +#endif \ No newline at end of file diff --git a/Engine/source/BadBehavior/core/Composite.cpp b/Engine/source/BadBehavior/core/Composite.cpp new file mode 100644 index 0000000000..6f3b4d9375 --- /dev/null +++ b/Engine/source/BadBehavior/core/Composite.cpp @@ -0,0 +1,110 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2014 Guy Allard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//----------------------------------------------------------------------------- + +#include "Composite.h" + +using namespace BadBehavior; + +//------------------------------------------------------------------------------ +// Base composite node +// override addObject to only allow behavior tree nodes to be added +//------------------------------------------------------------------------------ +void CompositeNode::addObject(SimObject *object) +{ + if(dynamic_cast(object)) + Parent::addObject(object); +} + +bool CompositeNode::acceptsAsChild( SimObject *object ) const +{ + return (dynamic_cast(object)); +} + +//------------------------------------------------------------------------------ +// Base composite task +//------------------------------------------------------------------------------ +CompositeTask::CompositeTask(Node &node, SimObject &owner, BehaviorTreeRunner &runner) + : Parent(node, owner, runner) +{ +} + +CompositeTask::~CompositeTask() +{ + while(mChildren.size()) + { + Task *child = mChildren.back(); + mChildren.pop_back(); + if(child) + delete child; + } +} + +void CompositeTask::onInitialize() +{ + if(mChildren.empty()) + { + CompositeNode *node = static_cast(mNodeRep); + for(SimSet::iterator i = node->begin(); i != node->end(); ++i) + { + Task *task = static_cast(*i)->createTask(*mOwner, *mRunner); + if(task) + { + task->setParent(this); + mChildren.push_back(task); + } + } + } + + mStatus = INVALID; + mCurrentChild = mChildren.begin(); + if(mCurrentChild != mChildren.end()) + (*mCurrentChild)->reset(); +} + +void CompositeTask::onTerminate() +{ + mStatus = INVALID; +} + +Task* CompositeTask::update() +{ + // reached the end of child list, we are complete + if (mCurrentChild == mChildren.end()) + mIsComplete = true; + + // task has finished + if( mIsComplete ) + { + // are we latent? + if(mStatus == RUNNING || mStatus == SUSPENDED) + mIsComplete = false; + + return NULL; + } + + // reset the child ready for the next tick + if(mStatus != RUNNING && mStatus != SUSPENDED) + (*mCurrentChild)->reset(); + + // return child + return (*mCurrentChild); +} \ No newline at end of file diff --git a/Engine/source/BadBehavior/core/Composite.h b/Engine/source/BadBehavior/core/Composite.h new file mode 100644 index 0000000000..47bc236d3b --- /dev/null +++ b/Engine/source/BadBehavior/core/Composite.h @@ -0,0 +1,70 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2014 Guy Allard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//----------------------------------------------------------------------------- + +#ifndef _BB_COMPOSITE_H_ +#define _BB_COMPOSITE_H_ + +#ifndef _BB_CORE_H_ +#include "Core.h" +#endif + +namespace BadBehavior +{ + //--------------------------------------------------------------------------- + // Composite node base class - for nodes with children + //--------------------------------------------------------------------------- + class CompositeNode : public Node + { + typedef Node Parent; + + public: + // override addObject and acceptsAsChild to only allow behavior tree nodes to be added as children + virtual void addObject(SimObject *obj); + virtual bool acceptsAsChild( SimObject *object ) const; + }; + + //--------------------------------------------------------------------------- + // Composite task base class + //--------------------------------------------------------------------------- + class CompositeTask : public Task + { + typedef Task Parent; + + protected: + // vector of pointers to child tasks + VectorPtr mChildren; + + // the current child task + VectorPtr::iterator mCurrentChild; + + virtual void onInitialize(); + virtual void onTerminate(); + virtual Task* update(); + + public: + CompositeTask(Node &node, SimObject &owner, BehaviorTreeRunner &runner); + virtual ~CompositeTask(); + }; + +} // namespace BadBehavior + +#endif diff --git a/Engine/source/BadBehavior/core/Core.cpp b/Engine/source/BadBehavior/core/Core.cpp new file mode 100644 index 0000000000..5991077ab9 --- /dev/null +++ b/Engine/source/BadBehavior/core/Core.cpp @@ -0,0 +1,160 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2014 Guy Allard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//----------------------------------------------------------------------------- + +#include "console/engineAPI.h" +#include "platform/profiler.h" +#include "Core.h" +#include "Runner.h" + +bool gInBtEditor = false; + +using namespace BadBehavior; + +//------------------------------------------------------------------------------ +// script enum for return type +//------------------------------------------------------------------------------ +ImplementEnumType( BehaviorReturnType, + "@brief The return status for a behavior.\n\n" + "@ingroup AI\n\n") + // not needed script side + //{ BadBehavior::INVALID, "INVALID", "The behavior could not be evaluated.\n" }, + { BadBehavior::SUCCESS, "SUCCESS", "The behavior succeeded.\n" }, + { BadBehavior::FAILURE, "FAILURE", "The behavior failed.\n" }, + { BadBehavior::RUNNING, "RUNNING", "The behavior is still running.\n" }, + // not needed script side + //{ BadBehavior::SUSPENDED, "SUSPENDED", "The behavior has been suspended.\n" }, + //{ BadBehavior::RESUME, "RESUME", "The behavior is resuming from suspended.\n" } +EndImplementEnumType; + + +//================================LeafNode====================================== + +//------------------------------------------------------------------------------ +// don't allow objects to be added +//------------------------------------------------------------------------------ +void LeafNode::addObject(SimObject *object) +{ +} + +bool LeafNode::acceptsAsChild( SimObject *object ) const +{ + return false; +} + + +//==================================Task======================================== + +Task::Task(Node &node, SimObject &owner, BehaviorTreeRunner &runner) + : mStatus(INVALID), + mIsComplete(false), + mNodeRep(&node), + mOwner(&owner), + mRunner(&runner), + mParent(NULL) +{ +} + +Task::~Task() +{ +} + +void Task::onInitialize() +{ +} + +void Task::onTerminate() +{ +} + +Task* Task::tick() +{ + PROFILE_SCOPE(Task_Tick); + + return update(); +} + +void Task::setup() +{ + PROFILE_SCOPE(Task_setup); + + if(mStatus != RUNNING && mStatus != SUSPENDED) + onInitialize(); + + mIsComplete = false; +} + +void Task::finish() +{ + if(mIsComplete) + onTerminate(); +} + +void Task::reset() +{ + mStatus = INVALID; +} + +Status Task::getStatus() +{ + return mStatus; +} + +void Task::setStatus(Status newStatus) +{ + mStatus = newStatus; +} + +void Task::setParent(Task *parent) +{ + mParent = parent; +} + +Task *Task::getParent() +{ + return mParent; +} + +void Task::onChildComplete(Status) +{ +} + +void Task::onResume() +{ + if(mStatus == SUSPENDED) + mStatus = RESUME; + + //Con::warnf("onResume %s", + // mNodeRep->getIdString()); +} + +DefineEngineFunction(onBehaviorTreeEditorStart, void, (),, + "@brief Notify the engine that the behavior tree editor is active") +{ + gInBtEditor = true; +} + + +DefineEngineFunction(onBehaviorTreeEditorStop, void, (),, + "@brief Notify the engine that the behavior tree editor has finished") +{ + gInBtEditor = false; +} \ No newline at end of file diff --git a/Engine/source/BadBehavior/core/Core.h b/Engine/source/BadBehavior/core/Core.h new file mode 100644 index 0000000000..a171a012b5 --- /dev/null +++ b/Engine/source/BadBehavior/core/Core.h @@ -0,0 +1,154 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2014 Guy Allard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//----------------------------------------------------------------------------- + +#ifndef _BBCORE_H_ +#define _BBCORE_H_ + +#ifndef _ENGINEAPI_H_ +#include "console/engineAPI.h" +#endif +#ifndef _SIMSET_H_ +#include "console/simSet.h" +#endif +#ifndef _SIMOBJECT_H_ +#include "console/simObject.h" +#endif + +extern bool gInBtEditor; + +namespace BadBehavior +{ + //--------------------------------------------------------------------------- + // return status values + //--------------------------------------------------------------------------- + enum Status + { + INVALID = -1, + FAILURE, + SUCCESS, + RUNNING, + SUSPENDED, + RESUME + }; + + class Task; + class BehaviorTreeRunner; + + //--------------------------------------------------------------------------- + // node base class + // derived from SimGroup for easy editor integration + //--------------------------------------------------------------------------- + class Node : public SimGroup + { + typedef SimGroup Parent; + + public: + // create a runtime task for this node + virtual Task* createTask(SimObject &owner, BehaviorTreeRunner &runner) = 0; + }; + + //--------------------------------------------------------------------------- + // Leaf node base class - for nodes without children + //--------------------------------------------------------------------------- + class LeafNode : public Node + { + typedef Node Parent; + + public: + virtual void addObject(SimObject *obj); + virtual bool acceptsAsChild( SimObject *object ) const; + }; + + + //--------------------------------------------------------------------------- + // base class for all behavior tree tasks + //--------------------------------------------------------------------------- + class Task + { + protected: + + // the current status + Status mStatus; + + // has the task finished + bool mIsComplete; + + // the node associated with this task + Node *mNodeRep; + + // the object that owns us + SimObjectPtr mOwner; + + // the object running us + BehaviorTreeRunner *mRunner; + + // the parent of this task + Task *mParent; + + // update + virtual Task* update() = 0; + + // initialize + virtual void onInitialize(); + + // terminate + virtual void onTerminate(); + + public: + // tasks are instantiated with a reference to their associated node + Task(Node &node, SimObject &owner, BehaviorTreeRunner &runner); + virtual ~Task(); + + // status sets and gets + virtual Status getStatus(); + void setStatus(Status newStatus); + + // parent sets and gets + void setParent(Task *parent); + Task *getParent(); + + // run the task + Task* tick(); + + // called when child task finishes + virtual void onChildComplete(Status); + + // called when a suspended task becomes active + virtual void onResume(); + + // prepare the task + void setup(); + + // finish the task + void finish(); + + // reset the task + void reset(); + }; + +} // namespace BadBehavior + +// make the return status enum accessible from script +typedef BadBehavior::Status BehaviorReturnType; +DefineEnumType( BehaviorReturnType ); + +#endif \ No newline at end of file diff --git a/Engine/source/BadBehavior/core/Decorator.cpp b/Engine/source/BadBehavior/core/Decorator.cpp new file mode 100644 index 0000000000..146e72ffd8 --- /dev/null +++ b/Engine/source/BadBehavior/core/Decorator.cpp @@ -0,0 +1,106 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2014 Guy Allard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//----------------------------------------------------------------------------- + +#include "Decorator.h" + +using namespace BadBehavior; + +//------------------------------------------------------------------------------ +// Base decorator node +// overrides for decorators to only allow 1 child +//------------------------------------------------------------------------------ +void DecoratorNode::addObject(SimObject *obj) +{ + if(empty()) + Parent::addObject(obj); +} + +bool DecoratorNode::acceptsAsChild( SimObject *object ) const +{ + return (dynamic_cast(object) && empty()); +} + +//------------------------------------------------------------------------------ +// Base decorator task +//------------------------------------------------------------------------------ +DecoratorTask::DecoratorTask(Node &node, SimObject &owner, BehaviorTreeRunner &runner) + : Parent(node, owner, runner), + mChild(NULL) +{ +} + +DecoratorTask::~DecoratorTask() +{ + if(mChild) + { + delete mChild; + mChild = NULL; + } +} + +void DecoratorTask::onInitialize() +{ + if(!mChild) + { + if(mNodeRep->size() > 0) + { + Node *childNode = static_cast(*mNodeRep->begin()); + if(childNode) + { + mChild = childNode->createTask(*mOwner, *mRunner); + if(mChild) + { + mChild->setParent(this); + mChild->reset(); + } + } + } + } + + mStatus = INVALID; +} + +void DecoratorTask::onTerminate() +{ + mStatus = INVALID; +} + +Task* DecoratorTask::update() +{ + // first time through, return child + if(!mIsComplete) + return mStatus != SUSPENDED ? mChild : NULL; + + // child has completed, are we latent? + if(mStatus == RUNNING || mStatus == SUSPENDED) + mIsComplete = false; + + // no more children + return NULL; +} + +void DecoratorTask::onChildComplete(Status s) +{ + // set our status to the child status and flag completed + mStatus = s; + mIsComplete = true; +} \ No newline at end of file diff --git a/Engine/source/BadBehavior/core/Decorator.h b/Engine/source/BadBehavior/core/Decorator.h new file mode 100644 index 0000000000..8aa4c70364 --- /dev/null +++ b/Engine/source/BadBehavior/core/Decorator.h @@ -0,0 +1,68 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2014 Guy Allard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//----------------------------------------------------------------------------- + +#ifndef _BB_DECORATOR_H_ +#define _BB_DECORATOR_H_ + +#ifndef _BB_CORE_H_ +#include "BadBehavior/core/Core.h" +#endif + +namespace BadBehavior +{ + //--------------------------------------------------------------------------- + // Decorator node base class + //--------------------------------------------------------------------------- + class DecoratorNode : public Node + { + typedef Node Parent; + + public: + // only allow 1 child node to be added + virtual void addObject(SimObject *obj); + virtual bool acceptsAsChild( SimObject *object ) const; + }; + + //--------------------------------------------------------------------------- + // Decorator task base class + //--------------------------------------------------------------------------- + class DecoratorTask : public Task + { + typedef Task Parent; + + protected: + Task* mChild; + + virtual Task* update(); + virtual void onInitialize(); + virtual void onTerminate(); + + public: + DecoratorTask(Node &node, SimObject &owner, BehaviorTreeRunner &runner); + virtual ~DecoratorTask(); + + virtual void onChildComplete(Status s); + }; + +} // namespace BadBehavior + +#endif diff --git a/Engine/source/BadBehavior/core/Runner.cpp b/Engine/source/BadBehavior/core/Runner.cpp new file mode 100644 index 0000000000..d024929042 --- /dev/null +++ b/Engine/source/BadBehavior/core/Runner.cpp @@ -0,0 +1,279 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2014 Guy Allard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//----------------------------------------------------------------------------- + +#include "console/engineAPI.h" +#include "platform/profiler.h" + +#include "Runner.h" + + +using namespace BadBehavior; + +IMPLEMENT_CONOBJECT(BehaviorTreeRunner); + +BehaviorTreeRunner::BehaviorTreeRunner() + : mOwner(NULL), + mRootNode(NULL), + mRootTask(NULL), + mIsRunning(0), + mTickEvent(0), + mTickFrequency(100) +{} + + +BehaviorTreeRunner::~BehaviorTreeRunner() +{ + delete mRootTask; + + if(Sim::isEventPending(mTickEvent)) + { + Sim::cancelEvent(mTickEvent); + mTickEvent = 0; + } +} + + +void BehaviorTreeRunner::initPersistFields() +{ + addGroup( "Behavior" ); + + addProtectedField("rootNode", TYPEID< SimObject >(), Offset(mRootNode, BehaviorTreeRunner), + &_setRootNode, &defaultProtectedGetFn, "@brief The root node of the tree to be run."); + + addProtectedField("ownerObject", TYPEID< SimObject >(), Offset(mOwner, BehaviorTreeRunner), + &_setOwner, &defaultProtectedGetFn, "@brief The object that owns the tree to be run."); + + addField("frequency", TypeS32, Offset(mTickFrequency, BehaviorTreeRunner), + "@brief The frequency in ms that the tree is ticked at."); + + endGroup( "Behavior" ); + + Parent::initPersistFields(); +} + +void BehaviorTreeRunner::onDeleteNotify(SimObject *object) +{ + // delete ourselves nicely + // - this takes care of any events registered to this runner + if(object == mOwner.getObject()) + safeDeleteObject(); +} + + +// processTick is where the magic happens :) +void BehaviorTreeRunner::onTick() +{ + PROFILE_SCOPE(BehaviorTreeRunner_processTick); + + // check that we are setup to run + if(mOwner.isNull() || mRootNode.isNull()) + return; + + if(gInBtEditor) + { + if(mRootTask) + reset(); + } + else + { + if(!mRootTask) + { + if((mRootTask = mRootNode->createTask(*mOwner, *this)) == NULL) + { + Con::errorf("BehaviorTreeTicker::processTick, no task for root node"); + return; + } + } + + // dispatch any signals + mSignalHandler.dispatchSignals(); + + // Evaluate the tree + mRootTask->setup(); + mRootTask->tick(); + //Con::warnf("Tree returned %s", EngineMarshallData(mRootTask->getStatus())); + mRootTask->finish(); + } + + // schedule the next tick + if(Sim::isEventPending(mTickEvent)) + Sim::cancelEvent(mTickEvent); + + mTickEvent = Sim::postEvent(this, new BehaviorTreeTickEvent(), Sim::getCurrentTime() + mTickFrequency); + + mIsRunning = true; +} + +void BehaviorTreeRunner::onReactivateEvent(Task *task) +{ + if(task) + task->onResume(); +} + +bool BehaviorTreeRunner::_setRootNode( void *object, const char *index, const char *data ) +{ + BehaviorTreeRunner *runner = static_cast( object ); + Node* root = NULL; + Sim::findObject( data, root ); + if(root) + runner->setRootNode(root); + return false; +} + +bool BehaviorTreeRunner::_setOwner( void *object, const char *index, const char *data ) +{ + BehaviorTreeRunner *runner = static_cast( object ); + SimObject* owner = NULL; + Sim::findObject( data, owner ); + if(owner) + runner->setOwner(owner); + return false; +} + +void BehaviorTreeRunner::setOwner(SimObject *owner) +{ + reset(); + mOwner = owner; + deleteNotify(mOwner); + start(); +} + + +void BehaviorTreeRunner::setRootNode(Node *root) +{ + reset(); + mRootNode = root; + start(); +} + + +void BehaviorTreeRunner::stop() +{ + if(Sim::isEventPending(mTickEvent)) + { + Sim::cancelEvent(mTickEvent); + mTickEvent = 0; + } + mIsRunning = false; +} + + +void BehaviorTreeRunner::start() +{ + if(Sim::isEventPending(mTickEvent)) + { + Sim::cancelEvent(mTickEvent); + mTickEvent = 0; + } + + mIsRunning = true; + if(mRootTask) + mRootTask->reset(); + + mTickEvent = Sim::postEvent(this, new BehaviorTreeTickEvent(), -1); +} + + +void BehaviorTreeRunner::reset() +{ + //stop(); + if(mRootTask) + { + delete mRootTask; + mRootTask = 0; + } + mSignalHandler.reset(); +} + + +void BehaviorTreeRunner::clear() +{ + reset(); + mRootNode = 0; +} + + +bool BehaviorTreeRunner::isRunning() +{ + return mIsRunning; +} + + +void BehaviorTreeRunner::subscribeToSignal(const char *signal, SignalSubscriber *subscriber) +{ + mSignalHandler.registerSubscriber(signal, subscriber); +} + + +void BehaviorTreeRunner::unsubscribeFromSignal(const char *signal, SignalSubscriber *subscriber) +{ + mSignalHandler.unregisterSubscriber(signal, subscriber); +} + + +void BehaviorTreeRunner::postSignal(const char *signal) +{ + mSignalHandler.postSignal(signal); +} + + +DefineEngineMethod( BehaviorTreeRunner, stop, void, (), , + "Halt the execution of the behavior tree.\n\n" + "@note The internal task status is retained, allowing execution to be resumed.") +{ + object->stop(); +} + + +DefineEngineMethod( BehaviorTreeRunner, start, void, (), , + "Resume execution of the (stopped) behavior tree.") +{ + object->start(); +} + + +DefineEngineMethod( BehaviorTreeRunner, reset, void, (), , + "Reset the behavior tree. Any internal state is lost.") +{ + object->reset(); +} + + +DefineEngineMethod( BehaviorTreeRunner, clear, void, (), , + "Clear the behavior tree.") +{ + object->clear(); +} + + +DefineEngineMethod( BehaviorTreeRunner, isRunning, bool, (), , + "Is the behavior tree running") +{ + return object->isRunning(); +} + + +DefineEngineMethod(BehaviorTreeRunner, postSignal, void, (const char *signal),, + "@brief Posts a signal to the behavior tree.\n\n") +{ + object->postSignal( signal ); +} \ No newline at end of file diff --git a/Engine/source/BadBehavior/core/Runner.h b/Engine/source/BadBehavior/core/Runner.h new file mode 100644 index 0000000000..ab601089bc --- /dev/null +++ b/Engine/source/BadBehavior/core/Runner.h @@ -0,0 +1,140 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2014 Guy Allard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//----------------------------------------------------------------------------- + +#ifndef _BB_RUNNER_H_ +#define _BB_RUNNER_H_ + +#ifndef _BB_CORE_H_ +#include "Core.h" +#endif +#ifndef _BB_SIGNAL_H_ +#include "Signal.h" +#endif +#ifndef _SIMOBJECT_H_ +#include "console/simObject.h" +#endif +#ifndef _SIMBASE_H_ +#include "console/simBase.h" +#endif +#ifndef _TVECTOR_H_ +#include "util/tVector.h" +#endif + +namespace BadBehavior +{ + + //--------------------------------------------------------------------------- + // BehaviorTreeRunner - handles the evaluation of the tree + //--------------------------------------------------------------------------- + class BehaviorTreeRunner : public SimObject + { + typedef SimObject Parent; + + private: + // is this tree running? + bool mIsRunning; + + // event ID of the tick event + U32 mTickEvent; + + // frequency of ticks in ms + U32 mTickFrequency; + + // the root node of the tree + SimObjectPtr mRootNode; + + // the task associated with the root node + Task *mRootTask; + + // the game object that is using this tree + SimObjectPtr mOwner; + + // signal handler for throwing signals around + SignalHandler mSignalHandler; + + // setters for the script interface + static bool _setRootNode( void *object, const char *index, const char *data ); + static bool _setOwner( void *object, const char *index, const char *data ); + + public: + /*Ctor*/ BehaviorTreeRunner(); + /*Dtor*/ ~BehaviorTreeRunner(); + + // public setters for the script interface + void setOwner(SimObject *owner); + void setRootNode(Node *root); + + // notification if our owner is deleted + virtual void onDeleteNotify(SimObject *object); + + // for script control + void stop(); + void start(); + void reset(); + void clear(); + bool isRunning(); + + // tick + void onTick(); + + // signal handling + void subscribeToSignal(const char *signal, SignalSubscriber *subscriber); + void unsubscribeFromSignal(const char *signal, SignalSubscriber *subscriber); + void postSignal(const char *signal); + + // task reactivation + void onReactivateEvent(Task *task); + + // script interface + static void initPersistFields(); + + DECLARE_CONOBJECT(BehaviorTreeRunner); + }; + + + class BehaviorTreeTickEvent : public SimEvent + { + public: + void process( SimObject *object ) + { + ((BehaviorTreeRunner*)object)->onTick(); + } + }; + + class TaskReactivateEvent : public SimEvent + { + Task *mTask; + public: + TaskReactivateEvent(Task &task) + { + mTask = &task; + } + + void process( SimObject *object ) + { + ((BehaviorTreeRunner*)object)->onReactivateEvent(mTask); + } + }; + +} // namespace BadBehavior + +#endif \ No newline at end of file diff --git a/Engine/source/BadBehavior/core/Signal.cpp b/Engine/source/BadBehavior/core/Signal.cpp new file mode 100644 index 0000000000..cd86fdce06 --- /dev/null +++ b/Engine/source/BadBehavior/core/Signal.cpp @@ -0,0 +1,168 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2014 Guy Allard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//----------------------------------------------------------------------------- + +#include "core/stringTable.h" + +#include "Signal.h" + +using namespace BadBehavior; + +// clean up +SignalHandler::~SignalHandler() +{ + clearSignalQueue(); + clearSubscribers(); +} + +void SignalHandler::clearSignalQueue() +{ + while(!mSignalQueue.empty()) + { + Signal *sig = mSignalQueue.back(); + mSignalQueue.pop_back(); + delete sig; + sig = NULL; + } +} + +void SignalHandler::clearSubscribers() +{ + for( Vector::const_iterator it = mSignals.begin(); it != mSignals.end(); ++it ) + { + // Delete the subscriber list. + VectorPtr* subscribers = mSubscribers.remove( *it ); + if(subscribers) + delete subscribers; + } +} + +void SignalHandler::reset() +{ + // empty out the signal queue + clearSignalQueue(); + + // clear out the subscribers table + clearSubscribers(); + + // reset the vector of registered signals + mSignals.clear(); +} + +// check if the specified signal is registered +bool SignalHandler::isSignalRegistered(const char *signal) +{ + StringTableEntry signalName = StringTable->insert( signal ); + return mSignals.contains(signalName); +} + +// register a signal +void SignalHandler::registerSignal(const char *signal) +{ + // check signal has a name + if(!signal || !signal[0]) + return; + + // Make sure the signal has not been registered yet. + if(!isSignalRegistered( signal ) ) + { + // Add to the signal list. + mSignals.push_back( StringTable->insert( signal ) ); + + // Create a list of subscribers for this event. + mSubscribers.insert( new VectorPtr, signal ); + } +} + +// register a subscriber to a signal +void SignalHandler::registerSubscriber(const char *signal, SignalSubscriber *subscriber) +{ + // must have a subscriber + if(!subscriber) + return; + + // and an signal name + if(!signal || !signal[0]) + return; + + // register a new event if this one hasn't already been registered + if(!isSignalRegistered(signal)) + registerSignal(signal); + + // add the subscriber if it's not already registered for this signal + VectorPtr* subscribers = mSubscribers.retreive( signal ); + + if(!subscribers->contains(subscriber)) + subscribers->push_back(subscriber); +} + +// unregister a subscriber +void SignalHandler::unregisterSubscriber(const char *signal, SignalSubscriber *subscriber) +{ + // check if the subscriber exists and that the signal is actually registered + if(!subscriber || !isSignalRegistered(signal)) + return; + + // find the subscriber and remove it + VectorPtr* subscribers = mSubscribers.retreive( signal ); + + for( VectorPtr::iterator iter = subscribers->begin(); iter != subscribers->end(); ++iter ) + { + if( *iter == subscriber ) + { + subscribers->erase_fast( iter ); + break; + } + } +} + +// post a signal +void SignalHandler::postSignal(const char *signal) +{ + // signal must be registered + if(!isSignalRegistered(signal)) + return; + + // dispatch a signal to each of the subscribers + VectorPtr* subscribers = mSubscribers.retreive( signal ); + + for( VectorPtr::iterator iter = subscribers->begin(); iter != subscribers->end(); ++iter ) + { + mSignalQueue.push_back(new Signal((*iter))); + } +} + + +// dispatch queued signals +void SignalHandler::dispatchSignals() +{ + if(mSignalQueue.empty()) + return; + + while(!mSignalQueue.empty()) + { + Signal *sig = mSignalQueue.back(); + mSignalQueue.pop_back(); + sig->send(); + delete sig; + sig = NULL; + } +} \ No newline at end of file diff --git a/Engine/source/BadBehavior/core/Signal.h b/Engine/source/BadBehavior/core/Signal.h new file mode 100644 index 0000000000..05f770303e --- /dev/null +++ b/Engine/source/BadBehavior/core/Signal.h @@ -0,0 +1,97 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2014 Guy Allard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//----------------------------------------------------------------------------- + +#ifndef _BB_SIGNAL_H_ +#define _BB_SIGNAL_H_ + +#ifndef _TVECTOR_H_ +#include "core/util/tVector.h" +#endif +#ifndef _TSIMPLEHASHTABLE_H +#include "core/tSimpleHashTable.h" +#endif + +namespace BadBehavior +{ + //--------------------------------------------------------------------------- + // Signal subscriber interface + //--------------------------------------------------------------------------- + class SignalSubscriber + { + protected: + virtual void subscribe() = 0; + virtual void unsubscribe() = 0; + + public: + virtual void onSignal() = 0; + }; + + //--------------------------------------------------------------------------- + // Signal class + //--------------------------------------------------------------------------- + class Signal + { + private: + SignalSubscriber *mSubscriber; + public: + Signal(SignalSubscriber *subscriber) : mSubscriber(subscriber) {} + void send() { if(mSubscriber) mSubscriber->onSignal(); } + }; + + //--------------------------------------------------------------------------- + // SignalHandler + // Simple handler to pass signals to tree task listeners + //--------------------------------------------------------------------------- + class SignalHandler + { + private: + SimpleHashTable> mSubscribers; + Vector mSignals; + + VectorPtr mSignalQueue; + + void registerSignal(const char *signal); + bool isSignalRegistered(const char *signal); + void clearSignalQueue(); + void clearSubscribers(); + + public: + SignalHandler() {} + ~SignalHandler(); + + // reset + void reset(); + + // register a scubscriber + void registerSubscriber(const char *signal, SignalSubscriber *subscriber); + + // unregister a subscriber + void unregisterSubscriber(const char *signal, SignalSubscriber *subscriber); + + // post an signal + void postSignal(const char *signal); + + // dispatch queued signals + void dispatchSignals(); + }; +} // namespace BadBehavior +#endif \ No newline at end of file diff --git a/Engine/source/BadBehavior/core/Stepper.cpp b/Engine/source/BadBehavior/core/Stepper.cpp new file mode 100644 index 0000000000..f919681bec --- /dev/null +++ b/Engine/source/BadBehavior/core/Stepper.cpp @@ -0,0 +1,71 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2014 Guy Allard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//----------------------------------------------------------------------------- + +#include "Stepper.h" + +using namespace BadBehavior; + +Status BehaviorTreeStepper::stepThrough(VectorPtr &taskVector) +{ + if(taskVector.empty()) return INVALID; + + if(taskVector.back()->getStatus() == SUSPENDED) + return SUSPENDED; + + Status status = INVALID; + + // loop through the tasks in the task list + while(!taskVector.empty()) + { + // get a task + Task *currentTask = taskVector.back(); + + // tick the task + Task *nextTask = currentTask->tick(); + + // if task returned no children, it has completed + if(!nextTask) + { + // stop if it's RUNNING or SUSPENED + status = currentTask->getStatus(); + if(status == RUNNING || status == SUSPENDED) + break; + + // otherwise, remove it from the list + taskVector.pop_back(); + if(!taskVector.empty()) + // and tell its parent that it completed + taskVector.back()->onChildComplete(currentTask->getStatus()); + + // complete the task + currentTask->finish(); + } + else + { + // add the child as a task + nextTask->setup(); + taskVector.push_back(nextTask); + } + } + + return status; +} \ No newline at end of file diff --git a/Engine/source/BadBehavior/core/Stepper.h b/Engine/source/BadBehavior/core/Stepper.h new file mode 100644 index 0000000000..78d2df5beb --- /dev/null +++ b/Engine/source/BadBehavior/core/Stepper.h @@ -0,0 +1,43 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2014 Guy Allard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//----------------------------------------------------------------------------- + +#ifndef _BB_STEPPER_H_ +#define _BB_STEPPER_H_ + +#ifndef _BB_CORE_H_ +#include "Core.h" +#endif + +namespace BadBehavior +{ + //--------------------------------------------------------------------------- + // helper class for stepping through a tree + //--------------------------------------------------------------------------- + class BehaviorTreeStepper + { + public: + static Status stepThrough(VectorPtr &taskVector); + }; + +} // namespace BadBehavior + +#endif \ No newline at end of file diff --git a/Engine/source/BadBehavior/core/behavior.cpp b/Engine/source/BadBehavior/core/behavior.cpp new file mode 100644 index 0000000000..7bb3ad6629 --- /dev/null +++ b/Engine/source/BadBehavior/core/behavior.cpp @@ -0,0 +1,106 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2014 Guy Allard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//----------------------------------------------------------------------------- + +#include "console/engineAPI.h" +#include "platform/profiler.h" + +#include "behavior.h" + +using namespace BadBehavior; + +//------------------------------------------------------------------------------ +// script enum for precondition mode +//------------------------------------------------------------------------------ +ImplementEnumType( BehaviorPreconditionType, + "@brief When should the precondition function be evaluated.\n\n" + "@ingroup AI\n\n") + { ONCE, "ONCE", "The first time the node is executed.\n" }, + { TICK, "TICK", "Each time the node is executed.\n" }, +EndImplementEnumType; + +//------------------------------------------------------------------------------ +// Behavior node +//------------------------------------------------------------------------------ +IMPLEMENT_CONOBJECT(Behavior); + +Behavior::Behavior() + : mPreconditionMode(ONCE) +{ +} + +void Behavior::initPersistFields() +{ + addGroup( "Behavior" ); + + addField( "preconditionMode", TYPEID< BadBehavior::PreconditionMode >(), Offset(mPreconditionMode, Behavior), + "@brief When to evaluate the precondition function."); + + endGroup( "Behavior" ); + + Parent::initPersistFields(); +} + +Task *Behavior::createTask(SimObject &owner, BehaviorTreeRunner &runner) +{ + return new BehaviorTask(*this, owner, runner); +} + +//------------------------------------------------------------------------------ +// ScriptedBehavior task +//------------------------------------------------------------------------------ +BehaviorTask::BehaviorTask(Node &node, SimObject &owner, BehaviorTreeRunner &runner) + : Parent(node, owner, runner) +{ +} + +Task* BehaviorTask::update() +{ + PROFILE_SCOPE(BehaviorTask_update); + + Behavior *node = static_cast(mNodeRep); + + // first check preconditions are valid + bool precondition = true; + if( (node->getPreconditionMode() == ONCE && mStatus == INVALID) || (node->getPreconditionMode() == TICK) ) + precondition = node->precondition( mOwner ); + + if(precondition) + { + // run onEnter if this is the first time the node is ticked + if(mStatus == INVALID) + node->onEnter(mOwner); + + // execute the main behavior and get its return value + mStatus = node->behavior(mOwner); + } + else + { + mStatus = FAILURE; + } + + mIsComplete = mStatus != RUNNING && mStatus != SUSPENDED; + + if(mIsComplete) + node->onExit(mOwner); + + return NULL; // leaves don't have children +} diff --git a/Engine/source/BadBehavior/core/behavior.h b/Engine/source/BadBehavior/core/behavior.h new file mode 100644 index 0000000000..cdc8e9482a --- /dev/null +++ b/Engine/source/BadBehavior/core/behavior.h @@ -0,0 +1,87 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2014 Guy Allard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//----------------------------------------------------------------------------- + +#ifndef _BB_BEHAVIOR_H_ +#define _BB_BEHAVIOR_H_ + +#ifndef _BB_CORE_H_ +#include "Core.h" +#endif + +namespace BadBehavior +{ + // specify when the precondition function should be executed + enum PreconditionMode + { + ONCE, // the first time the behavior is evaluated + TICK // every tick + }; + + //--------------------------------------------------------------------------- + // Behavior - Base class for structured behavior leaf nodes + //--------------------------------------------------------------------------- + class Behavior : public LeafNode + { + typedef LeafNode Parent; + + protected: + // how often should we valuate the precondition + PreconditionMode mPreconditionMode; + + public: + Behavior(); + + virtual Task *createTask(SimObject &owner, BehaviorTreeRunner &runner); + + static void initPersistFields(); + + const PreconditionMode &getPreconditionMode() const { return mPreconditionMode; } + + virtual bool precondition( SimObject *owner ) { return true; } + virtual void onEnter( SimObject *owner ) {} + virtual void onExit( SimObject *owner ) {} + virtual Status behavior( SimObject *owner ) { return SUCCESS; } + + DECLARE_CONOBJECT(Behavior); + }; + + //--------------------------------------------------------------------------- + // Behavior task + //--------------------------------------------------------------------------- + class BehaviorTask : public Task + { + typedef Task Parent; + + protected: + virtual Task* update(); + + public: + BehaviorTask(Node &node, SimObject &owner, BehaviorTreeRunner &runner); + }; + +} // namespace BadBehavior + +// make the return precondition mode accessible from script +typedef BadBehavior::PreconditionMode BehaviorPreconditionType; +DefineEnumType( BehaviorPreconditionType ); + +#endif \ No newline at end of file diff --git a/Engine/source/BadBehavior/decorator/FailAlways.cpp b/Engine/source/BadBehavior/decorator/FailAlways.cpp new file mode 100644 index 0000000000..49c2d08524 --- /dev/null +++ b/Engine/source/BadBehavior/decorator/FailAlways.cpp @@ -0,0 +1,51 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2014 Guy Allard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//----------------------------------------------------------------------------- + +#include "FailAlways.h" + +using namespace BadBehavior; + +//------------------------------------------------------------------------------ +// FailAlways decorator node +//------------------------------------------------------------------------------ +IMPLEMENT_CONOBJECT(FailAlways); + +Task *FailAlways::createTask(SimObject &owner, BehaviorTreeRunner &runner) +{ + return new FailAlwaysTask(*this, owner, runner); +} + +//------------------------------------------------------------------------------ +// FailAlways decorator task +//------------------------------------------------------------------------------ +FailAlwaysTask::FailAlwaysTask(Node &node, SimObject &owner, BehaviorTreeRunner &runner) + : Parent(node, owner, runner) +{ +} + +void FailAlwaysTask::onChildComplete(Status s) +{ + Parent::onChildComplete(s); + if(mStatus == SUCCESS) + mStatus = FAILURE; +} + diff --git a/Engine/source/BadBehavior/decorator/FailAlways.h b/Engine/source/BadBehavior/decorator/FailAlways.h new file mode 100644 index 0000000000..65ba3766cc --- /dev/null +++ b/Engine/source/BadBehavior/decorator/FailAlways.h @@ -0,0 +1,61 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2014 Guy Allard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//----------------------------------------------------------------------------- + +#ifndef _BB_FAILALWAYS_H_ +#define _BB_FAILALWAYS_H_ + +#ifndef _BB_DECORATOR_H_ +#include "BadBehavior/core/Decorator.h" +#endif + +namespace BadBehavior +{ + //--------------------------------------------------------------------------- + // FailAlways decorator + // Returns FAILURE if child returns SUCCESS or FAILURE. RUNNING and INVALID are unchanged + //--------------------------------------------------------------------------- + class FailAlways : public DecoratorNode + { + typedef DecoratorNode Parent; + + public: + virtual Task *createTask(SimObject &owner, BehaviorTreeRunner &runner); + + DECLARE_CONOBJECT(FailAlways); + }; + + //--------------------------------------------------------------------------- + // FailAlways task + //--------------------------------------------------------------------------- + class FailAlwaysTask : public DecoratorTask + { + typedef DecoratorTask Parent; + + public: + FailAlwaysTask(Node &node, SimObject &owner, BehaviorTreeRunner &runner); + + virtual void onChildComplete(Status s); + }; + +} // namespace BadBehavior + +#endif \ No newline at end of file diff --git a/Engine/source/BadBehavior/decorator/Inverter.cpp b/Engine/source/BadBehavior/decorator/Inverter.cpp new file mode 100644 index 0000000000..0defd9ab79 --- /dev/null +++ b/Engine/source/BadBehavior/decorator/Inverter.cpp @@ -0,0 +1,54 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2014 Guy Allard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//----------------------------------------------------------------------------- + +#include "Inverter.h" + +using namespace BadBehavior; + +//------------------------------------------------------------------------------ +// Inverter decorator node +//------------------------------------------------------------------------------ +IMPLEMENT_CONOBJECT(Inverter); + +Task *Inverter::createTask(SimObject &owner, BehaviorTreeRunner &runner) +{ + return new InverterTask(*this, owner, runner); +} + +//------------------------------------------------------------------------------ +// Inverter decorator task +//------------------------------------------------------------------------------ +InverterTask::InverterTask(Node &node, SimObject &owner, BehaviorTreeRunner &runner) + : Parent(node, owner, runner) +{ +} + +void InverterTask::onChildComplete(Status s) +{ + Parent::onChildComplete(s); + + // invert SUCCESS or FAILURE, leave INVALID and RUNNING unchanged + if (mStatus == SUCCESS) + mStatus = FAILURE; + else if (mStatus == FAILURE) + mStatus = SUCCESS; +} \ No newline at end of file diff --git a/Engine/source/BadBehavior/decorator/Inverter.h b/Engine/source/BadBehavior/decorator/Inverter.h new file mode 100644 index 0000000000..0cc38537e5 --- /dev/null +++ b/Engine/source/BadBehavior/decorator/Inverter.h @@ -0,0 +1,62 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2014 Guy Allard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//----------------------------------------------------------------------------- + +#ifndef _BB_INVERTER_H_ +#define _BB_INVERTER_H_ + +#ifndef _BB_DECORATOR_H_ +#include "BadBehavior/core/Decorator.h" +#endif + +namespace BadBehavior +{ + //--------------------------------------------------------------------------- + // inverter decorator + // invert the return value of the child, + // SUCCESS becomes FAILURE, FAILURE becomes SUCCESS, INVALID and RUNNING are unmodified + //--------------------------------------------------------------------------- + class Inverter : public DecoratorNode + { + typedef DecoratorNode Parent; + + public: + virtual Task *createTask(SimObject &owner, BehaviorTreeRunner &runner); + + DECLARE_CONOBJECT(Inverter); + }; + + //--------------------------------------------------------------------------- + // inverter decorator task + //--------------------------------------------------------------------------- + class InverterTask : public DecoratorTask + { + typedef DecoratorTask Parent; + + public: + InverterTask(Node &node, SimObject &owner, BehaviorTreeRunner &runner); + + virtual void onChildComplete(Status s); + }; + +} // namespace BadBehavior + +#endif \ No newline at end of file diff --git a/Engine/source/BadBehavior/decorator/Loop.cpp b/Engine/source/BadBehavior/decorator/Loop.cpp new file mode 100644 index 0000000000..d873aab865 --- /dev/null +++ b/Engine/source/BadBehavior/decorator/Loop.cpp @@ -0,0 +1,118 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2014 Guy Allard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//----------------------------------------------------------------------------- + +#include "Loop.h" + +using namespace BadBehavior; + +//------------------------------------------------------------------------------ +// Loop decorator node +//------------------------------------------------------------------------------ +IMPLEMENT_CONOBJECT(Loop); + +ImplementEnumType( LoopTerminationPolicy, + "@brief The policy to use when determining if the loop should terminate early.\n\n" + "@ingroup AI\n\n") + { Loop::ON_FAILURE, "ON_FAILURE", "Will terminate and return FAILURE if child fails.\n" }, + { Loop::ON_SUCCESS, "ON_SUCCESS", "Will terminate and return SUCCESS if child succeeds.\n" }, +EndImplementEnumType; + +Loop::Loop() + : mNumLoops(0), + mTerminationPolicy(ON_FAILURE) +{ +} + +void Loop::initPersistFields() +{ + addGroup( "Behavior" ); + + addProtectedField( "numLoops", TypeS32, Offset(mNumLoops, Loop), &_setNumLoops, &defaultProtectedGetFn, + "The number of times to repeat the child behavior. 0 = infinite." ); + + addField( "terminationPolicy", TYPEID< Loop::TerminationPolicy >(), Offset(mTerminationPolicy, Loop), + "@brief The policy to use when deciding if the loop should terminate before completion."); + + endGroup( "Behavior" ); + + Parent::initPersistFields(); +} + +bool Loop::_setNumLoops(void *object, const char *index, const char *data) +{ + Loop *node = static_cast( object ); + node->mNumLoops = getMax(0, dAtoi( data )); + return false; +} + +Task *Loop::createTask(SimObject &owner, BehaviorTreeRunner &runner) +{ + return new LoopTask(*this, owner, runner); +} + +//------------------------------------------------------------------------------ +// Loop decorator task +//------------------------------------------------------------------------------ +LoopTask::LoopTask(Node &node, SimObject &owner, BehaviorTreeRunner &runner) + : Parent(node, owner, runner), + mCurrentLoop(0) +{ +} + +void LoopTask::onInitialize() +{ + Parent::onInitialize(); + mCurrentLoop = 0; +} + +Task* LoopTask::update() +{ + if(Parent::update()) + return mChild; + + if(mStatus == RUNNING || mStatus == SUSPENDED) + mIsComplete = false; + + // child has terminated with SUCCESS or FAILURE + if( mIsComplete ) + { + // check if we should continue looping or reset + Loop *nodeRep = static_cast(mNodeRep); + Loop::TerminationPolicy policy = nodeRep->getTerminationPolicy(); + S32 numLoops = nodeRep->getNumLoops(); + + // termination policy not met? + if( ((policy == Loop::ON_FAILURE) && (mStatus != FAILURE)) || + ((policy == Loop::ON_SUCCESS) && (mStatus != SUCCESS)) ) + { + // more looping to be done + if ( (++mCurrentLoop < numLoops) || (numLoops == 0) ) + { + mIsComplete = false; + mStatus = RUNNING; + } + } + } + + // no children to return + return NULL; +} diff --git a/Engine/source/BadBehavior/decorator/Loop.h b/Engine/source/BadBehavior/decorator/Loop.h new file mode 100644 index 0000000000..2b5deb5be0 --- /dev/null +++ b/Engine/source/BadBehavior/decorator/Loop.h @@ -0,0 +1,90 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2014 Guy Allard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//----------------------------------------------------------------------------- + +#ifndef _BB_LOOP_H_ +#define _BB_LOOP_H_ + +#ifndef _BB_DECORATOR_H_ +#include "BadBehavior/core/Decorator.h" +#endif + +namespace BadBehavior +{ + //--------------------------------------------------------------------------- + // loop decorator + // repeats the child behaviour for n times, or until it fails + // returns RUNNING until it completes + //--------------------------------------------------------------------------- + class Loop : public DecoratorNode + { + typedef DecoratorNode Parent; + + public: + // loop termination policies + enum TerminationPolicy + { + ON_FAILURE, + ON_SUCCESS, + }; + + protected: + static bool _setNumLoops(void *object, const char *index, const char *data); + S32 mNumLoops; + TerminationPolicy mTerminationPolicy; + + public: + Loop(); + + virtual Task *createTask(SimObject &owner, BehaviorTreeRunner &runner); + + static void initPersistFields(); + + S32 getNumLoops() const { return mNumLoops; } + TerminationPolicy getTerminationPolicy() const { return mTerminationPolicy; } + + DECLARE_CONOBJECT(Loop); + }; + + //--------------------------------------------------------------------------- + // loop task + //--------------------------------------------------------------------------- + class LoopTask : public DecoratorTask + { + typedef DecoratorTask Parent; + + protected: + S32 mCurrentLoop; + + virtual void onInitialize(); + virtual Task *update(); + + public: + LoopTask(Node &node, SimObject &owner, BehaviorTreeRunner &runner); + }; + +} // namespace BadBehavior + +// make the loop termination policy enum accessible from script +typedef BadBehavior::Loop::TerminationPolicy LoopTerminationPolicy; +DefineEnumType( LoopTerminationPolicy ); + +#endif \ No newline at end of file diff --git a/Engine/source/BadBehavior/decorator/Monitor.cpp b/Engine/source/BadBehavior/decorator/Monitor.cpp new file mode 100644 index 0000000000..685af1e7e6 --- /dev/null +++ b/Engine/source/BadBehavior/decorator/Monitor.cpp @@ -0,0 +1,52 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2014 Guy Allard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//----------------------------------------------------------------------------- + +#include "Monitor.h" + +using namespace BadBehavior; + +//------------------------------------------------------------------------------ +// Monitor decorator node +//------------------------------------------------------------------------------ +IMPLEMENT_CONOBJECT(Monitor); + +Task *Monitor::createTask(SimObject &owner, BehaviorTreeRunner &runner) +{ + return new MonitorTask(*this, owner, runner); +} + +//------------------------------------------------------------------------------ +// Logger decorator task +//------------------------------------------------------------------------------ +MonitorTask::MonitorTask(Node &node, SimObject &owner, BehaviorTreeRunner &runner) + : Parent(node, owner, runner) +{ +} + +void MonitorTask::onChildComplete(Status s) +{ + Parent::onChildComplete(s); + + Con::printf("%s (%s) child returning %s", static_cast(mNodeRep)->getInternalName(), + static_cast(mNodeRep)->getIdString(), + EngineMarshallData< BehaviorReturnType > (mStatus)); +} \ No newline at end of file diff --git a/Engine/source/BadBehavior/decorator/Monitor.h b/Engine/source/BadBehavior/decorator/Monitor.h new file mode 100644 index 0000000000..2fbd707ba2 --- /dev/null +++ b/Engine/source/BadBehavior/decorator/Monitor.h @@ -0,0 +1,61 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2014 Guy Allard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//----------------------------------------------------------------------------- + +#ifndef _BB_MONITOR_H_ +#define _BB_MONITOR_H_ + +#ifndef _BB_DECORATOR_H_ +#include "BadBehavior/core/Decorator.h" +#endif + +namespace BadBehavior +{ + //--------------------------------------------------------------------------- + // Monitor decorator + // outputs the return status to the console, + //--------------------------------------------------------------------------- + class Monitor : public DecoratorNode + { + typedef DecoratorNode Parent; + + public: + virtual Task *createTask(SimObject &owner, BehaviorTreeRunner &runner); + + DECLARE_CONOBJECT(Monitor); + }; + + //--------------------------------------------------------------------------- + // Monitor decorator task + //--------------------------------------------------------------------------- + class MonitorTask : public DecoratorTask + { + typedef DecoratorTask Parent; + + public: + MonitorTask(Node &node, SimObject &owner, BehaviorTreeRunner &runner); + + virtual void onChildComplete(Status s); + }; + +} // namespace BadBehavior + +#endif \ No newline at end of file diff --git a/Engine/source/BadBehavior/decorator/Root.cpp b/Engine/source/BadBehavior/decorator/Root.cpp new file mode 100644 index 0000000000..42f10a5dc8 --- /dev/null +++ b/Engine/source/BadBehavior/decorator/Root.cpp @@ -0,0 +1,75 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2014 Guy Allard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//----------------------------------------------------------------------------- + +#include "Root.h" + +using namespace BadBehavior; + +//------------------------------------------------------------------------------ +// Root decorator node +//------------------------------------------------------------------------------ +IMPLEMENT_CONOBJECT(Root); + +Task *Root::createTask(SimObject &owner, BehaviorTreeRunner &runner) +{ + return new RootTask(*this, owner, runner); +} + +//------------------------------------------------------------------------------ +// Root decorator task +//------------------------------------------------------------------------------ +RootTask::RootTask(Node &node, SimObject &owner, BehaviorTreeRunner &runner) + : Parent(node, owner, runner), + mBranch(NULL) +{ +} + +RootTask::~RootTask() +{ + if(mBranch) + delete mBranch; +} + +void RootTask::onInitialize() +{ + Parent::onInitialize(); + if(!mBranch) + mBranch = new BehaviorTreeBranch(mChild); + else + mBranch->reset(); +} + +Task *RootTask::update() +{ + mStatus = mBranch->update(); + + mIsComplete = mStatus != RUNNING && mStatus != SUSPENDED; + return NULL; +} + +Status RootTask::getStatus() +{ + if(mStatus == SUSPENDED) + return mBranch->getStatus(); + + return mStatus; +} \ No newline at end of file diff --git a/Engine/source/BadBehavior/decorator/Root.h b/Engine/source/BadBehavior/decorator/Root.h new file mode 100644 index 0000000000..4b9a971152 --- /dev/null +++ b/Engine/source/BadBehavior/decorator/Root.h @@ -0,0 +1,73 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2014 Guy Allard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//----------------------------------------------------------------------------- + +#ifndef _BB_ROOT_H_ +#define _BB_ROOT_H_ + +#ifndef _BB_BRANCH_H_ +#include "BadBehavior/core/Branch.h" +#endif +#ifndef _BB_DECORATOR_H_ +#include "BadBehavior/core/Decorator.h" +#endif + +namespace BadBehavior +{ + //--------------------------------------------------------------------------- + // Root decorator + // A placeholder node to mark the start of a tree. + // Used by the editor, bypassed during tree evaluation. + //--------------------------------------------------------------------------- + class Root : public DecoratorNode + { + typedef DecoratorNode Parent; + + public: + virtual Task* createTask(SimObject &owner, BehaviorTreeRunner &runner); + + DECLARE_CONOBJECT(Root); + }; + + //--------------------------------------------------------------------------- + // Root task + //--------------------------------------------------------------------------- + class RootTask : public DecoratorTask + { + typedef DecoratorTask Parent; + + protected: + BehaviorTreeBranch *mBranch; + + virtual Task* update(); + virtual void onInitialize(); + //virtual void onTerminate(); + + public: + RootTask(Node &node, SimObject &owner, BehaviorTreeRunner &runner); + virtual ~RootTask(); + + virtual Status getStatus(); + }; + +} // namespace BadBehavior + +#endif \ No newline at end of file diff --git a/Engine/source/BadBehavior/decorator/SucceedAlways.cpp b/Engine/source/BadBehavior/decorator/SucceedAlways.cpp new file mode 100644 index 0000000000..fb6846234c --- /dev/null +++ b/Engine/source/BadBehavior/decorator/SucceedAlways.cpp @@ -0,0 +1,51 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2014 Guy Allard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//----------------------------------------------------------------------------- + +#include "SucceedAlways.h" + +using namespace BadBehavior; + +//------------------------------------------------------------------------------ +// SucceedAlways decorator node +//------------------------------------------------------------------------------ +IMPLEMENT_CONOBJECT(SucceedAlways); + +Task *SucceedAlways::createTask(SimObject &owner, BehaviorTreeRunner &runner) +{ + return new SucceedAlwaysTask(*this, owner, runner); +} + +//------------------------------------------------------------------------------ +// SucceedAlways decorator task +//------------------------------------------------------------------------------ +SucceedAlwaysTask::SucceedAlwaysTask(Node &node, SimObject &owner, BehaviorTreeRunner &runner) + : Parent(node, owner, runner) +{ +} + +void SucceedAlwaysTask::onChildComplete(Status s) +{ + Parent::onChildComplete(s); + if(mStatus == FAILURE) + mStatus = SUCCESS; +} + diff --git a/Engine/source/BadBehavior/decorator/SucceedAlways.h b/Engine/source/BadBehavior/decorator/SucceedAlways.h new file mode 100644 index 0000000000..cdb1c02045 --- /dev/null +++ b/Engine/source/BadBehavior/decorator/SucceedAlways.h @@ -0,0 +1,61 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2014 Guy Allard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//----------------------------------------------------------------------------- + +#ifndef _BB_SUCCEEDALWAYS_H_ +#define _BB_SUCCEEDALWAYS_H_ + +#ifndef _BB_DECORATOR_H_ +#include "BadBehavior/core/Decorator.h" +#endif + +namespace BadBehavior +{ + //--------------------------------------------------------------------------- + // SucceedAlways decorator + // Returns SUCCESS if child returns SUCCESS or FAILURE. RUNNING and INVALID are unchanged + //--------------------------------------------------------------------------- + class SucceedAlways : public DecoratorNode + { + typedef DecoratorNode Parent; + + public: + virtual Task *createTask(SimObject &owner, BehaviorTreeRunner &runner); + + DECLARE_CONOBJECT(SucceedAlways); + }; + + //--------------------------------------------------------------------------- + // SucceedAlways task + //--------------------------------------------------------------------------- + class SucceedAlwaysTask : public DecoratorTask + { + typedef DecoratorTask Parent; + + public: + SucceedAlwaysTask(Node &node, SimObject &owner, BehaviorTreeRunner &runner); + + virtual void onChildComplete(Status s); + }; + +} // namespace BadBehavior + +#endif \ No newline at end of file diff --git a/Engine/source/BadBehavior/decorator/Ticker.cpp b/Engine/source/BadBehavior/decorator/Ticker.cpp new file mode 100644 index 0000000000..ab6fd3d546 --- /dev/null +++ b/Engine/source/BadBehavior/decorator/Ticker.cpp @@ -0,0 +1,134 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2014 Guy Allard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//----------------------------------------------------------------------------- + +#include "BadBehavior/core/Runner.h" +#include "Ticker.h" + +using namespace BadBehavior; + +//------------------------------------------------------------------------------ +// Ticker decorator node +// **** EXPERIMENTAL **** +//------------------------------------------------------------------------------ +IMPLEMENT_CONOBJECT(Ticker); + +Ticker::Ticker() + : mFrequencyMs(100) +{ +} + +void Ticker::initPersistFields() +{ + addGroup( "Behavior" ); + + addProtectedField( "frequencyMs", TypeS32, Offset(mFrequencyMs, Ticker), &_setFrequency, &defaultProtectedGetFn, + "The time to wait between evaluations of this nodes child." ); + + endGroup( "Behavior" ); + + Parent::initPersistFields(); +} + +bool Ticker::_setFrequency(void *object, const char *index, const char *data) +{ + Ticker *node = static_cast( object ); + node->mFrequencyMs = getMax(0, dAtoi( data )); + return false; +} + +Task *Ticker::createTask(SimObject &owner, BehaviorTreeRunner &runner) +{ + return new TickerTask(*this, owner, runner); +} + +//------------------------------------------------------------------------------ +// Ticker decorator task +//------------------------------------------------------------------------------ +TickerTask::TickerTask(Node &node, SimObject &owner, BehaviorTreeRunner &runner) + : Parent(node, owner, runner), + mNextTimeMs(0), + mEventId(0), + mBranch(NULL) +{ +} + +TickerTask::~TickerTask() +{ + cancelEvent(); + + if(mBranch) + delete mBranch; +} + +void TickerTask::cancelEvent() +{ + if(Sim::isEventPending(mEventId)) + { + Sim::cancelEvent(mEventId); + mEventId = 0; + } +} + +void TickerTask::onInitialize() +{ + Parent::onInitialize(); + cancelEvent(); + if(!mBranch) + mBranch = new BehaviorTreeBranch(mChild); + else + mBranch->reset(); +} + +void TickerTask::onTerminate() +{ + Parent::onTerminate(); + cancelEvent(); +} + +Task* TickerTask::update() +{ + if(Sim::getCurrentTime() < mNextTimeMs) + { + if(!Sim::isEventPending(mEventId)) + mEventId = Sim::postEvent(mRunner, new TaskReactivateEvent(*this), mNextTimeMs); + + mStatus = SUSPENDED; + } + else if(mStatus != SUSPENDED) + { + mNextTimeMs = Sim::getCurrentTime() + static_cast(mNodeRep)->getFrequencyMs(); + Status s = mBranch->update(); + mStatus = s != SUSPENDED ? s : RUNNING; + } + + mIsComplete = mStatus != SUSPENDED && mStatus != RUNNING; + + return NULL; +} + +Status TickerTask::getStatus() +{ + if(mStatus != SUSPENDED && mStatus != RESUME) + return mBranch->getStatus(); + + return mStatus; +} \ No newline at end of file diff --git a/Engine/source/BadBehavior/decorator/Ticker.h b/Engine/source/BadBehavior/decorator/Ticker.h new file mode 100644 index 0000000000..06b96ea076 --- /dev/null +++ b/Engine/source/BadBehavior/decorator/Ticker.h @@ -0,0 +1,89 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2014 Guy Allard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//----------------------------------------------------------------------------- + +#ifndef _BB_TICKER_H_ +#define _BB_TICKER_H_ + +#ifndef _BB_DECORATOR_H_ +#include "BadBehavior/core/Decorator.h" +#endif +#ifndef _BB_BRANCH_H_ +#include "BadBehavior/core/Branch.h" +#endif + +namespace BadBehavior +{ + //--------------------------------------------------------------------------- + // Ticker decorator + // Limits the frequency at which it's children are updated. + //--------------------------------------------------------------------------- + class Ticker : public DecoratorNode + { + typedef DecoratorNode Parent; + + protected: + static bool _setFrequency(void *object, const char *index, const char *data); + + // time between ticks (in ms) + S32 mFrequencyMs; + + public: + Ticker(); + + virtual Task *createTask(SimObject &owner, BehaviorTreeRunner &runner); + + static void initPersistFields(); + + S32 getFrequencyMs() const { return mFrequencyMs; } + + DECLARE_CONOBJECT(Ticker); + }; + + //--------------------------------------------------------------------------- + // Ticker decorator task + //--------------------------------------------------------------------------- + class TickerTask : public DecoratorTask + { + typedef DecoratorTask Parent; + + protected: + U32 mNextTimeMs; + U32 mEventId; + + BehaviorTreeBranch *mBranch; + + virtual void onInitialize(); + virtual void onTerminate(); + virtual Task* update(); + + void cancelEvent(); + + public: + TickerTask(Node &node, SimObject &owner, BehaviorTreeRunner &runner); + virtual ~TickerTask(); + + virtual Status getStatus(); + }; + +} // namespace BadBehavior + +#endif \ No newline at end of file diff --git a/Engine/source/BadBehavior/leaf/RandomWait.cpp b/Engine/source/BadBehavior/leaf/RandomWait.cpp new file mode 100644 index 0000000000..b668387b0f --- /dev/null +++ b/Engine/source/BadBehavior/leaf/RandomWait.cpp @@ -0,0 +1,124 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2014 Guy Allard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//----------------------------------------------------------------------------- + +#include "math/mMathFn.h" + +#include "BadBehavior/core/Runner.h" +#include "RandomWait.h" + +using namespace BadBehavior; + +//------------------------------------------------------------------------------ +// RandomWait leaf node +//------------------------------------------------------------------------------ +IMPLEMENT_CONOBJECT(RandomWait); + +RandomWait::RandomWait() + : mWaitMinMs(0), + mWaitMaxMs(99999) +{ +} + +void RandomWait::initPersistFields() +{ + addGroup( "Behavior" ); + + addProtectedField( "waitMinMs", TypeS32, Offset(mWaitMinMs, RandomWait), &_setWaitMin, &defaultProtectedGetFn, + "The minimum time period in ms to wait before completion." ); + + addProtectedField( "waitMaxMs", TypeS32, Offset(mWaitMaxMs, RandomWait), &_setWaitMax, &defaultProtectedGetFn, + "The maximum time period in ms to wait before completion." ); + + endGroup( "Behavior" ); + + Parent::initPersistFields(); +} + +bool RandomWait::_setWaitMin(void *object, const char *index, const char *data) +{ + RandomWait *node = static_cast( object ); + node->mWaitMinMs = getMin(node->mWaitMaxMs, dAtoi( data )); + return false; +} + +bool RandomWait::_setWaitMax(void *object, const char *index, const char *data) +{ + RandomWait *node = static_cast( object ); + node->mWaitMaxMs = getMax(node->mWaitMinMs, dAtoi( data )); + return false; +} + +Task *RandomWait::createTask(SimObject &owner, BehaviorTreeRunner &runner) +{ + return new RandomWaitTask(*this, owner, runner); +} + +//------------------------------------------------------------------------------ +// RandomWait task +//------------------------------------------------------------------------------ +RandomWaitTask::RandomWaitTask(Node &node, SimObject &owner, BehaviorTreeRunner &runner) + : Parent(node, owner, runner) +{ +} + +RandomWaitTask::~RandomWaitTask() +{ + cancelEvent(); +} + +void RandomWaitTask::cancelEvent() +{ + if(Sim::isEventPending(mEventId)) + { + Sim::cancelEvent(mEventId); + mEventId = 0; + } +} + +void RandomWaitTask::onInitialize() +{ + Parent::onInitialize(); + cancelEvent(); +} + +void RandomWaitTask::onTerminate() +{ + Parent::onTerminate(); + cancelEvent(); +} + +Task* RandomWaitTask::update() +{ + if(mStatus == RESUME) + { + mStatus = SUCCESS; + mIsComplete = true; + } + else if(mStatus == INVALID) + { + RandomWait *node = static_cast(mNodeRep); + mEventId = Sim::postEvent(mRunner, new TaskReactivateEvent(*this), Sim::getCurrentTime() + mRandI(node->getWaitMinMs(), node->getWaitMaxMs())); + mStatus = SUSPENDED; + } + + return NULL; +} diff --git a/Engine/source/BadBehavior/leaf/RandomWait.h b/Engine/source/BadBehavior/leaf/RandomWait.h new file mode 100644 index 0000000000..b83e0f4c12 --- /dev/null +++ b/Engine/source/BadBehavior/leaf/RandomWait.h @@ -0,0 +1,83 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2014 Guy Allard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//----------------------------------------------------------------------------- + +#ifndef _BB_RANDOMWAIT_H_ +#define _BB_RANDOMWAIT_H_ + +#ifndef _BB_CORE_H_ +#include "BadBehavior/core/Core.h" +#endif + +namespace BadBehavior +{ + //--------------------------------------------------------------------------- + // RandomWait leaf + // Pauses for a random period of time between delayMin and delayMax ms before completing. + //--------------------------------------------------------------------------- + class RandomWait : public LeafNode + { + typedef LeafNode Parent; + + protected: + static bool _setWaitMin(void *object, const char *index, const char *data); + static bool _setWaitMax(void *object, const char *index, const char *data); + + S32 mWaitMinMs; + S32 mWaitMaxMs; + + public: + RandomWait(); + + virtual Task *createTask(SimObject &owner, BehaviorTreeRunner &runner); + + static void initPersistFields(); + + S32 getWaitMinMs() const { return mWaitMinMs; } + S32 getWaitMaxMs() const { return mWaitMaxMs; } + + DECLARE_CONOBJECT(RandomWait); + }; + + //--------------------------------------------------------------------------- + // RandomWait leaf task + //--------------------------------------------------------------------------- + class RandomWaitTask : public Task + { + typedef Task Parent; + + protected: + U32 mEventId; + + virtual void onInitialize(); + virtual void onTerminate(); + virtual Task* update(); + + void cancelEvent(); + + public: + RandomWaitTask(Node &node, SimObject &owner, BehaviorTreeRunner &runner); + virtual ~RandomWaitTask(); + }; + +} // namespace BadBehavior + +#endif \ No newline at end of file diff --git a/Engine/source/BadBehavior/leaf/ScriptEval.cpp b/Engine/source/BadBehavior/leaf/ScriptEval.cpp new file mode 100644 index 0000000000..cc155408bf --- /dev/null +++ b/Engine/source/BadBehavior/leaf/ScriptEval.cpp @@ -0,0 +1,94 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2014 Guy Allard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//----------------------------------------------------------------------------- + +#include "console/engineAPI.h" +#include "platform/profiler.h" + +#include "ScriptEval.h" + +using namespace BadBehavior; + +//------------------------------------------------------------------------------ +// ScriptEval node - warning, slow! +//------------------------------------------------------------------------------ +IMPLEMENT_CONOBJECT(ScriptEval); + +ScriptEval::ScriptEval() + : mDefaultReturnStatus(SUCCESS) +{ +} + +void ScriptEval::initPersistFields() +{ + addGroup( "Behavior" ); + + addField( "behaviorScript", TypeCommand, Offset(mBehaviorScript, ScriptEval), + "@brief The command to execute when the leaf is ticked. Max 255 characters." ); + + addField( "defaultReturnStatus", TYPEID< BadBehavior::Status >(), Offset(mDefaultReturnStatus, ScriptEval), + "@brief The default value for this node to return."); + + endGroup( "Behavior" ); + + Parent::initPersistFields(); +} + +Task *ScriptEval::createTask(SimObject &owner, BehaviorTreeRunner &runner) +{ + return new ScriptEvalTask(*this, owner, runner); +} + +Status ScriptEval::evaluateScript( SimObject *owner ) +{ + PROFILE_SCOPE(ScriptEval_evaluateScript); + + if(mBehaviorScript.isEmpty()) + return mDefaultReturnStatus; + + // get the result + const char *result = Con::evaluatef("%%obj=%s; %s return %s;", + owner->getIdString(), + mBehaviorScript.c_str(), + EngineMarshallData< BehaviorReturnType >(mDefaultReturnStatus)); + + if(result[0] == '1' || result[0] == '0') + // map true or false to SUCCEED or FAILURE + return static_cast(dAtoi(result)); + + // convert the returned value to our internal enum type + return EngineUnmarshallData< BehaviorReturnType >()( result ); +} + +//------------------------------------------------------------------------------ +// RunScript task +//------------------------------------------------------------------------------ +ScriptEvalTask::ScriptEvalTask(Node &node, SimObject &owner, BehaviorTreeRunner &runner) + : Parent(node, owner, runner) +{ +} + +Task* ScriptEvalTask::update() +{ + mStatus = static_cast(mNodeRep)->evaluateScript( mOwner ); + + return NULL; // leaves don't have children +} \ No newline at end of file diff --git a/Engine/source/BadBehavior/leaf/ScriptEval.h b/Engine/source/BadBehavior/leaf/ScriptEval.h new file mode 100644 index 0000000000..23be24b185 --- /dev/null +++ b/Engine/source/BadBehavior/leaf/ScriptEval.h @@ -0,0 +1,75 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2014 Guy Allard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//----------------------------------------------------------------------------- + +#ifndef _BB_SCRIPTEVAL_H_ +#define _BB_SCRIPTEVAL_H_ + +#ifndef _BB_CORE_H_ +#include "BadBehavior/core/Core.h" +#endif + +namespace BadBehavior +{ + //--------------------------------------------------------------------------- + // ScriptEval - evaluates a set of Torque Script commands when run + // Warning - slow, handle with care! + //--------------------------------------------------------------------------- + class ScriptEval : public LeafNode + { + typedef LeafNode Parent; + + private: + // status to return if the command does not return a value + Status mDefaultReturnStatus; + + // the torque script to evaluate + String mBehaviorScript; + + public: + ScriptEval(); + + virtual Task *createTask(SimObject &owner, BehaviorTreeRunner &runner); + + static void initPersistFields(); + + // execute the script + Status evaluateScript(SimObject *owner); + + DECLARE_CONOBJECT(ScriptEval); + }; + + //--------------------------------------------------------------------------- + // ScriptEval task + //--------------------------------------------------------------------------- + class ScriptEvalTask : public Task + { + typedef Task Parent; + + protected: + virtual Task* update(); + + public: + ScriptEvalTask(Node &node, SimObject &owner, BehaviorTreeRunner &runner); + }; + +} // namespace BadBehavior +#endif \ No newline at end of file diff --git a/Engine/source/BadBehavior/leaf/ScriptFunc.cpp b/Engine/source/BadBehavior/leaf/ScriptFunc.cpp new file mode 100644 index 0000000000..ab6ebe9df1 --- /dev/null +++ b/Engine/source/BadBehavior/leaf/ScriptFunc.cpp @@ -0,0 +1,121 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2014 Guy Allard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//----------------------------------------------------------------------------- + +#include "console/engineAPI.h" +#include "platform/profiler.h" + +#include "ScriptFunc.h" + +using namespace BadBehavior; + +//------------------------------------------------------------------------------ +// ScriptFunc node +//------------------------------------------------------------------------------ +IMPLEMENT_CONOBJECT(ScriptFunc); + +ScriptFunc::ScriptFunc() + : mDefaultReturnStatus(SUCCESS), + mScriptFunction(StringTable->insert("")) +{ + for(U8 i = 0; i < MAX_COMMAND_ARGS; ++i) + mScriptArgs[i] = StringTable->insert(""); +} + + +void ScriptFunc::initPersistFields() +{ + addGroup( "Behavior" ); + + addField( "func", TypeCaseString, Offset(mScriptFunction, ScriptFunc), + "@brief The function to execute when the leaf is ticked." ); + + addField( "args", TypeCaseString, Offset(mScriptArgs, ScriptFunc), MAX_COMMAND_ARGS, + "@brief The arguments to be passed to the function to be executed." ); + + addField( "defaultReturnStatus", TYPEID< BadBehavior::Status >(), Offset(mDefaultReturnStatus, ScriptFunc), + "@brief The default value for this node to return."); + + endGroup( "Behavior" ); + + Parent::initPersistFields(); +} + +Task *ScriptFunc::createTask(SimObject &owner, BehaviorTreeRunner &runner) +{ + return new ScriptFuncTask(*this, owner, runner); +} + +Status ScriptFunc::evaluate( SimObject *owner ) +{ + PROFILE_SCOPE(ScriptFunc_evaluate); + + if(!owner) + return INVALID; + + if(!mScriptFunction || !mScriptFunction[0]) + return mDefaultReturnStatus; + + S32 argc = 0; + + const char *args[MAX_COMMAND_ARGS + 2]; + args[0] = mScriptFunction; + + while(argc < MAX_COMMAND_ARGS && (mScriptArgs[argc] && mScriptArgs[argc][0])) + { + args[argc + 2] = mScriptArgs[argc]; + ++argc; + } + + argc += 2; + + // get the result + const char *result = Con::execute(owner, argc, args); + + // if function didn't return a result, return our default status + if(!result || !result[0]) + return mDefaultReturnStatus; + + // map true or false to SUCCEED or FAILURE + if(result[0] == '1' || result[0] == '0') + return static_cast(dAtoi(result)); + + // convert the returned value to our internal enum type + return EngineUnmarshallData< BehaviorReturnType >()( result ); +} + +//------------------------------------------------------------------------------ +// ScriptFunc task +//------------------------------------------------------------------------------ +ScriptFuncTask::ScriptFuncTask(Node &node, SimObject &owner, BehaviorTreeRunner &runner) + : Parent(node, owner, runner) +{ +} + +Task* ScriptFuncTask::update() +{ + mStatus = static_cast(mNodeRep)->evaluate( mOwner ); + + if(mStatus != RUNNING && mStatus != SUSPENDED) + mIsComplete = true; + + return NULL; // leaves don't have children +} \ No newline at end of file diff --git a/Engine/source/BadBehavior/leaf/ScriptFunc.h b/Engine/source/BadBehavior/leaf/ScriptFunc.h new file mode 100644 index 0000000000..af264db96b --- /dev/null +++ b/Engine/source/BadBehavior/leaf/ScriptFunc.h @@ -0,0 +1,79 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2014 Guy Allard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//----------------------------------------------------------------------------- + +#ifndef _BB_SCRIPTFUNC_H_ +#define _BB_SCRIPTFUNC_H_ + +#ifndef _BB_CORE_H_ +#include "BadBehavior/core/Core.h" +#endif + +namespace BadBehavior +{ + static const U8 MAX_COMMAND_ARGS = 4; + + //--------------------------------------------------------------------------- + // ScriptFunc - executes a function on the owner object + //--------------------------------------------------------------------------- + class ScriptFunc : public LeafNode + { + typedef LeafNode Parent; + + protected: + // the function to call + StringTableEntry mScriptFunction; + + // the arguments for the function + StringTableEntry mScriptArgs[MAX_COMMAND_ARGS]; + + // status to return if the command does not return a value + Status mDefaultReturnStatus; + + public: + ScriptFunc(); + + virtual Task *createTask(SimObject &owner, BehaviorTreeRunner &runner); + + static void initPersistFields(); + + // execute the command + Status evaluate(SimObject *owner); + + DECLARE_CONOBJECT(ScriptFunc); + }; + + //--------------------------------------------------------------------------- + // ScriptFunc task + //--------------------------------------------------------------------------- + class ScriptFuncTask : public Task + { + typedef Task Parent; + + protected: + virtual Task* update(); + + public: + ScriptFuncTask(Node &node, SimObject &owner, BehaviorTreeRunner &runner); + }; + +} // namespace BadBehavior +#endif \ No newline at end of file diff --git a/Engine/source/BadBehavior/leaf/ScriptedBehavior.cpp b/Engine/source/BadBehavior/leaf/ScriptedBehavior.cpp new file mode 100644 index 0000000000..54f7bffff2 --- /dev/null +++ b/Engine/source/BadBehavior/leaf/ScriptedBehavior.cpp @@ -0,0 +1,83 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2014 Guy Allard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//----------------------------------------------------------------------------- + +#include "console/engineAPI.h" +#include "platform/profiler.h" + +#include "ScriptedBehavior.h" + +using namespace BadBehavior; + +//------------------------------------------------------------------------------ +// ScriptedBehavior node +//------------------------------------------------------------------------------ +IMPLEMENT_CONOBJECT(ScriptedBehavior); + +IMPLEMENT_CALLBACK( ScriptedBehavior, onEnter, void, ( SimObject* owner ), ( owner ), + "Called when the behavior is first run.\n" + "@param owner The object that this behavior tree belongs to." ); + +IMPLEMENT_CALLBACK( ScriptedBehavior, onExit, void, ( SimObject* owner ), ( owner ), + "Called when the behavior is complete.\n" + "@param owner The object that this behavior tree belongs to." ); + +IMPLEMENT_CALLBACK( ScriptedBehavior, precondition, bool, ( SimObject* owner ), ( owner ), + "Called prior to evaluating the behavior.\n" + "@param owner The object that this behavior tree belongs to.\n" + "A return value of false indicates that evaluation of the behavior should stop.\n" + "A return value of true indicates that evaluation of the behavior should continue."); + +IMPLEMENT_CALLBACK( ScriptedBehavior, behavior, Status, ( SimObject* owner ), ( owner ), + "Evaluate the main behavior of this node.\n" + "@param owner The object that this behavior tree belongs to.\n"); + +ScriptedBehavior::ScriptedBehavior() +{ +} + +bool ScriptedBehavior::precondition( SimObject *owner ) +{ + PROFILE_SCOPE( ScriptedBehavior_precondition); + + if(isMethod("precondition")) + return precondition_callback(owner); + + return true; +} + +void ScriptedBehavior::onEnter( SimObject *owner ) +{ + PROFILE_SCOPE( ScriptedBehavior_onEnter ); + onEnter_callback(owner); +} + +void ScriptedBehavior::onExit( SimObject *owner ) +{ + PROFILE_SCOPE( ScriptedBehavior_onExit ); + onExit_callback(owner); +} + +Status ScriptedBehavior::behavior( SimObject *owner ) +{ + PROFILE_SCOPE( ScriptedBehavior_behavior ); + return behavior_callback( owner ); +} \ No newline at end of file diff --git a/Engine/source/BadBehavior/leaf/ScriptedBehavior.h b/Engine/source/BadBehavior/leaf/ScriptedBehavior.h new file mode 100644 index 0000000000..8dddc8f027 --- /dev/null +++ b/Engine/source/BadBehavior/leaf/ScriptedBehavior.h @@ -0,0 +1,56 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2014 Guy Allard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//----------------------------------------------------------------------------- + +#ifndef _BB_SCRIPTEDBEHAVIOR_H_ +#define _BB_SCRIPTEDBEHAVIOR_H_ + +#ifndef _BB_CORE_H_ +#include "BadBehavior/core/behavior.h" +#endif + +namespace BadBehavior +{ + //--------------------------------------------------------------------------- + // ScriptedBehavior - A structured behavior leaf node which defines a series + // Of scripted callbacks + //--------------------------------------------------------------------------- + class ScriptedBehavior : public Behavior + { + typedef Behavior Parent; + + public: + ScriptedBehavior(); + + virtual bool precondition( SimObject *owner ); + virtual void onEnter( SimObject *owner ); + virtual void onExit( SimObject *owner ); + virtual Status behavior( SimObject *owner ); + + DECLARE_CONOBJECT(ScriptedBehavior); + DECLARE_CALLBACK(void, onEnter, (SimObject *owner)); + DECLARE_CALLBACK(void, onExit, (SimObject *owner)); + DECLARE_CALLBACK(bool, precondition, (SimObject *owner)); + DECLARE_CALLBACK(Status, behavior, (SimObject *owner)); + }; +} // namespace BadBehavior + +#endif \ No newline at end of file diff --git a/Engine/source/BadBehavior/leaf/SubTree.cpp b/Engine/source/BadBehavior/leaf/SubTree.cpp new file mode 100644 index 0000000000..b07ca7d2f3 --- /dev/null +++ b/Engine/source/BadBehavior/leaf/SubTree.cpp @@ -0,0 +1,74 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2014 Guy Allard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//----------------------------------------------------------------------------- + +#include "SubTree.h" + +using namespace BadBehavior; + + +//------------------------------------------------------------------------------ +// SubTree node - links to another behavior tree +//------------------------------------------------------------------------------ +IMPLEMENT_CONOBJECT(SubTree); + +SubTree::SubTree() + : mSubTreeName(0) +{ +} + +void SubTree::initPersistFields() +{ + addGroup( "Behavior" ); + + addField( "subTreeName", TypeString, Offset(mSubTreeName, SubTree), + "@brief The name of the behavior tree that this node links to. Max 255 characters." ); + + endGroup( "Behavior" ); + + Parent::initPersistFields(); +} + +Task *SubTree::createTask(SimObject &owner, BehaviorTreeRunner &runner) +{ + if(!mSubTreeName || mSubTreeName[0] == 0) + { + Con::errorf("SubTree::onInitialize: no sub tree specified"); + return NULL; + } + + SimObject *subTreeNode; + + if(!Sim::findObject(mSubTreeName, subTreeNode)) + { + Con::errorf("SubTree:onInitialize: the specified sub tree does not exist"); + return NULL; + } + + Node *node = dynamic_cast(subTreeNode); + if(!node) + { + Con::errorf("SubTree::onInitialize: the specified sub tree is not a behavior tree node"); + return NULL; + } + + return node->createTask(owner, runner); +} \ No newline at end of file diff --git a/Engine/source/BadBehavior/leaf/SubTree.h b/Engine/source/BadBehavior/leaf/SubTree.h new file mode 100644 index 0000000000..01481eb4d7 --- /dev/null +++ b/Engine/source/BadBehavior/leaf/SubTree.h @@ -0,0 +1,56 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2014 Guy Allard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//----------------------------------------------------------------------------- + +#ifndef _BB_SUBTREE_H_ +#define _BB_SUBTREE_H_ + +#ifndef _BB_CORE_H_ +#include "BadBehavior/core/Core.h" +#endif + +namespace BadBehavior +{ + //--------------------------------------------------------------------------- + // SubTree decorator + // Turns a named Behavior into a subtree of this node. + // Does not actually have a child, so is a subclass of leaf + //--------------------------------------------------------------------------- + class SubTree : public LeafNode + { + typedef LeafNode Parent; + + protected: + const char* mSubTreeName; + + public: + SubTree(); + + virtual Task *createTask(SimObject &owner, BehaviorTreeRunner &runner); + + static void initPersistFields(); + + DECLARE_CONOBJECT(SubTree); + }; + +} // namespace BadBehavior + +#endif \ No newline at end of file diff --git a/Engine/source/BadBehavior/leaf/Wait.cpp b/Engine/source/BadBehavior/leaf/Wait.cpp new file mode 100644 index 0000000000..1e7164ded0 --- /dev/null +++ b/Engine/source/BadBehavior/leaf/Wait.cpp @@ -0,0 +1,113 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2014 Guy Allard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//----------------------------------------------------------------------------- + +#include "math/mMathFn.h" + +#include "BadBehavior/core/Runner.h" +#include "Wait.h" + +using namespace BadBehavior; + +//------------------------------------------------------------------------------ +// Wait leaf node +//------------------------------------------------------------------------------ +IMPLEMENT_CONOBJECT(Wait); + +Wait::Wait() + : mWaitMs(1000) +{ +} + +void Wait::initPersistFields() +{ + addGroup( "Behavior" ); + + addProtectedField( "waitMs", TypeS32, Offset(mWaitMs, Wait), &_setWait, &defaultProtectedGetFn, + "The time in ms that the node should wait before completion." ); + + endGroup( "Behavior" ); + + Parent::initPersistFields(); +} + +bool Wait::_setWait(void *object, const char *index, const char *data) +{ + Wait *node = static_cast( object ); + node->mWaitMs = getMax(0, dAtoi( data )); + return false; +} + +Task *Wait::createTask(SimObject &owner, BehaviorTreeRunner &runner) +{ + return new WaitTask(*this, owner, runner); +} + +//------------------------------------------------------------------------------ +// Wait task +//------------------------------------------------------------------------------ +WaitTask::WaitTask(Node &node, SimObject &owner, BehaviorTreeRunner &runner) + : Parent(node, owner, runner), + mEventId(0) +{ +} + +WaitTask::~WaitTask() +{ + cancelEvent(); +} + +void WaitTask::cancelEvent() +{ + if(Sim::isEventPending(mEventId)) + { + Sim::cancelEvent(mEventId); + mEventId = 0; + } +} + +void WaitTask::onInitialize() +{ + Parent::onInitialize(); + cancelEvent(); +} + +void WaitTask::onTerminate() +{ + Parent::onTerminate(); + cancelEvent(); +} + +Task* WaitTask::update() +{ + if(mStatus == RESUME) + { + mStatus = SUCCESS; + mIsComplete = true; + } + else if(mStatus == INVALID) + { + mEventId = Sim::postEvent(mRunner, new TaskReactivateEvent(*this), Sim::getCurrentTime() + static_cast(mNodeRep)->getWaitMs()); + mStatus = SUSPENDED; + } + + return NULL; +} diff --git a/Engine/source/BadBehavior/leaf/Wait.h b/Engine/source/BadBehavior/leaf/Wait.h new file mode 100644 index 0000000000..20a49ee010 --- /dev/null +++ b/Engine/source/BadBehavior/leaf/Wait.h @@ -0,0 +1,80 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2014 Guy Allard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//----------------------------------------------------------------------------- + +#ifndef _BB_WAIT_H_ +#define _BB_WAIT_H_ + +#ifndef _BB_CORE_H_ +#include "BadBehavior/core/Core.h" +#endif + +namespace BadBehavior +{ + //--------------------------------------------------------------------------- + // Wait leaf + // Pauses for a set time period. + //--------------------------------------------------------------------------- + class Wait : public LeafNode + { + typedef LeafNode Parent; + + protected: + static bool _setWait(void *object, const char *index, const char *data); + + S32 mWaitMs; + + public: + Wait(); + + virtual Task *createTask(SimObject &owner, BehaviorTreeRunner &runner); + + static void initPersistFields(); + + S32 getWaitMs() const { return mWaitMs; } + + DECLARE_CONOBJECT(Wait); + }; + + //--------------------------------------------------------------------------- + // Wait leaf task + //--------------------------------------------------------------------------- + class WaitTask : public Task + { + typedef Task Parent; + + protected: + U32 mEventId; + + virtual void onInitialize(); + virtual void onTerminate(); + virtual Task* update(); + + void cancelEvent(); + + public: + WaitTask(Node &node, SimObject &owner, BehaviorTreeRunner &runner); + virtual ~WaitTask(); + }; + +} // namespace BadBehavior + +#endif \ No newline at end of file diff --git a/Engine/source/BadBehavior/leaf/WaitForSignal.cpp b/Engine/source/BadBehavior/leaf/WaitForSignal.cpp new file mode 100644 index 0000000000..437ca6ab65 --- /dev/null +++ b/Engine/source/BadBehavior/leaf/WaitForSignal.cpp @@ -0,0 +1,143 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2014 Guy Allard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//----------------------------------------------------------------------------- + +#include "BadBehavior/core/Runner.h" +#include "WaitForSignal.h" + +using namespace BadBehavior; + +//------------------------------------------------------------------------------ +// WaitForSignal leaf node +//------------------------------------------------------------------------------ +IMPLEMENT_CONOBJECT(WaitForSignal); + +WaitForSignal::WaitForSignal() + : mTimeoutMs(0) +{ +} + +void WaitForSignal::initPersistFields() +{ + addGroup( "Behavior" ); + + addField( "signalName", TypeRealString, Offset(mSignalName, WaitForSignal), + "The time in ms that the node should wait before completion." ); + + addProtectedField( "timeoutMs", TypeS32, Offset(mTimeoutMs, WaitForSignal), &_setTimeout, &defaultProtectedGetFn, + "The maximum time in ms that the node should wait for the signal.\n" + "A value of 0 indicates no limit"); + + endGroup( "Behavior" ); + + Parent::initPersistFields(); +} + +Task *WaitForSignal::createTask(SimObject &owner, BehaviorTreeRunner &runner) +{ + return new WaitForSignalTask(*this, owner, runner); +} + +bool WaitForSignal::_setTimeout(void *object, const char *index, const char *data) +{ + WaitForSignal *node = static_cast( object ); + node->mTimeoutMs = getMax(0, dAtoi( data )); + return false; +} + +//------------------------------------------------------------------------------ +// Wait task +//------------------------------------------------------------------------------ +WaitForSignalTask::WaitForSignalTask(Node &node, SimObject &owner, BehaviorTreeRunner &runner) + : Parent(node, owner, runner), + mEventId(0) +{ +} + +WaitForSignalTask::~WaitForSignalTask() +{ + unsubscribe(); + cancelEvent(); +} + +void WaitForSignalTask::subscribe() +{ + mRunner->subscribeToSignal(static_cast(mNodeRep)->getSignalName().c_str(), this); +} + +void WaitForSignalTask::unsubscribe() +{ + mRunner->unsubscribeFromSignal(static_cast(mNodeRep)->getSignalName().c_str(), this); +} + +void WaitForSignalTask::onSignal() +{ + onResume(); +} + +void WaitForSignalTask::onTimeout() +{ + mStatus = FAILURE; +} + +void WaitForSignalTask::cancelEvent() +{ + if(mEventId != 0 && Sim::isEventPending(mEventId)) + { + Sim::cancelEvent(mEventId); + mEventId = 0; + } +} + +void WaitForSignalTask::onInitialize() +{ + Parent::onInitialize(); + subscribe(); + cancelEvent(); +} + +void WaitForSignalTask::onTerminate() +{ + Parent::onTerminate(); + unsubscribe(); + cancelEvent(); +} + +Task* WaitForSignalTask::update() +{ + if(mStatus == RESUME) + { + mStatus = SUCCESS; + } + else if (mStatus == INVALID) + { + mStatus = SUSPENDED; + + S32 timeout = static_cast(mNodeRep)->getTimeoutMs(); + if(timeout > 0) + mEventId = Sim::postEvent(mRunner, new WaitForSignalTimeoutEvent(*this), Sim::getCurrentTime() + timeout); + } + + if(mStatus == SUCCESS || mStatus == FAILURE) + mIsComplete = true; + + return NULL; +} diff --git a/Engine/source/BadBehavior/leaf/WaitForSignal.h b/Engine/source/BadBehavior/leaf/WaitForSignal.h new file mode 100644 index 0000000000..53e9e1591f --- /dev/null +++ b/Engine/source/BadBehavior/leaf/WaitForSignal.h @@ -0,0 +1,111 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2014 Guy Allard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//----------------------------------------------------------------------------- + +#ifndef _BB_WAITFORSIGNAL_H_ +#define _BB_WAITFORSIGNAL_H_ + +#ifndef _BB_CORE_H_ +#include "BadBehavior/core/Core.h" +#endif +#ifndef _BB_SIGNAL_H_ +#include "BadBehavior/core/Signal.h" +#endif + +namespace BadBehavior +{ + //--------------------------------------------------------------------------- + // WaitForSignal leaf + // Waits until it receives the requested signal. + //--------------------------------------------------------------------------- + class WaitForSignal : public LeafNode + { + typedef LeafNode Parent; + + protected: + String mSignalName; + S32 mTimeoutMs; + + static bool _setTimeout(void *object, const char *index, const char *data); + + public: + WaitForSignal(); + + virtual Task *createTask(SimObject &owner, BehaviorTreeRunner &runner); + + static void initPersistFields(); + + const String &getSignalName() const { return mSignalName; } + S32 getTimeoutMs() const { return mTimeoutMs; } + + DECLARE_CONOBJECT(WaitForSignal); + }; + + //--------------------------------------------------------------------------- + // WaitForSignal leaf task + //--------------------------------------------------------------------------- + class WaitForSignalTask : public Task, public virtual SignalSubscriber + { + typedef Task Parent; + + protected: + U32 mEventId; + + virtual void onInitialize(); + virtual void onTerminate(); + virtual Task* update(); + + void cancelEvent(); + + // SignalSubscriber + virtual void subscribe(); + virtual void unsubscribe(); + + public: + WaitForSignalTask(Node &node, SimObject &owner, BehaviorTreeRunner &runner); + virtual ~WaitForSignalTask(); + + // SignalSubscriber + virtual void onSignal(); + + // timeout + void onTimeout(); + }; + + + class WaitForSignalTimeoutEvent : public SimEvent + { + WaitForSignalTask *mTask; + public: + WaitForSignalTimeoutEvent(WaitForSignalTask &task) + { + mTask = &task; + } + + void process( SimObject *object ) + { + mTask->onTimeout(); + } + }; + +} // namespace BadBehavior + +#endif \ No newline at end of file diff --git a/Engine/source/BadBehavior/leaf/compiled/followBehaviorAction.cpp b/Engine/source/BadBehavior/leaf/compiled/followBehaviorAction.cpp new file mode 100644 index 0000000000..df1bfc28f6 --- /dev/null +++ b/Engine/source/BadBehavior/leaf/compiled/followBehaviorAction.cpp @@ -0,0 +1,109 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2014 Guy Allard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//----------------------------------------------------------------------------- + +#include "FollowBehaviorAction.h" + +#include "T3D/aiPlayer.h" + +using namespace BadBehavior; + +//------------------------------------------------------------------------------ +// FollowBehavior node +//------------------------------------------------------------------------------ +IMPLEMENT_CONOBJECT(FollowBehaviorAction); + +FollowBehaviorAction::FollowBehaviorAction() +{ +} + +bool FollowBehaviorAction::precondition( SimObject *owner ) +{ + PROFILE_SCOPE( FollowBehaviorAction_precondition); + + // check that our owner is an AIPlayer + AIPlayer *aiPlayer = dynamic_cast(owner); + if(!aiPlayer) + return false; + + return true; +} + +void FollowBehaviorAction::onEnter( SimObject *owner ) +{ + //PROFILE_SCOPE( FollowBehaviorAction_onEnter ); +} + +void FollowBehaviorAction::onExit( SimObject *owner ) +{ + //PROFILE_SCOPE( FollowBehaviorAction_onExit ); +} + +Status FollowBehaviorAction::behavior( SimObject *owner ) +{ + PROFILE_SCOPE( FollowBehaviorAction_behavior ); + + // get the owning AIPlayer object + AIPlayer *aiPlayer = dynamic_cast(owner); + if(!aiPlayer) + return FAILURE; + + // get the script-specified followObject + const char *followFieldName = StringTable->insert("followObject"); + const char *followFieldValue = owner->getDataField(followFieldName, NULL); + if(!followFieldValue || !followFieldValue[0]) + return FAILURE; + + SimObject *followSimObj = Sim::findObject(dAtoi(followFieldValue)); + if(!followSimObj) + return FAILURE; + + // check that the follow object is a SceneObject + SceneObject *followObject = dynamic_cast(followSimObj); + if(!followObject) + return FAILURE; + + // get the script-specified followDistance field + const char *distanceFieldName = StringTable->insert("followDistance"); + const char *distanceFieldValue = owner->getDataField(distanceFieldName, NULL); + float followDistance = 1.f; + if(distanceFieldValue && distanceFieldValue[0]) + followDistance = dAtof(distanceFieldValue); + + // try and stay at followDistance from the followObject + Point3F targetPos = followObject->getPosition(); + Point3F followVec = aiPlayer->getPosition() - targetPos; + + // get current distance (ignore z component) + F32 curDist = Point3F(followVec.x, followVec.y, 0.f).len(); + + if(mFabs(curDist - followDistance) > aiPlayer->getMoveTolerance()) + { + followVec.normalize(); + followVec *= followDistance; + Point3F destination = targetPos + followVec; + + aiPlayer->setMoveDestination(destination, true); + return RUNNING; + } + + return SUCCESS; +} \ No newline at end of file diff --git a/Engine/source/BadBehavior/leaf/compiled/followBehaviorAction.h b/Engine/source/BadBehavior/leaf/compiled/followBehaviorAction.h new file mode 100644 index 0000000000..9410c0324a --- /dev/null +++ b/Engine/source/BadBehavior/leaf/compiled/followBehaviorAction.h @@ -0,0 +1,52 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2014 Guy Allard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//----------------------------------------------------------------------------- + +#ifndef _BB_FOLLOWBEHAVIORACTION_H_ +#define _BB_FOLLOWBEHAVIORACTION_H_ + +#ifndef _BB_CORE_H_ +#include "BadBehavior/core/behavior.h" +#endif + +namespace BadBehavior +{ + //--------------------------------------------------------------------------- + // FollowBehaviorAction - Make an AIPlayer follow another object + // Demonstrates how to create a compiled behavior by subclassing Behavior + //--------------------------------------------------------------------------- + class FollowBehaviorAction : public Behavior + { + typedef Behavior Parent; + + public: + FollowBehaviorAction(); + + virtual bool precondition( SimObject *owner ); + virtual void onEnter( SimObject *owner ); + virtual void onExit( SimObject *owner ); + virtual Status behavior( SimObject *owner ); + + DECLARE_CONOBJECT(FollowBehaviorAction); + }; +} // namespace BadBehavior + +#endif \ No newline at end of file diff --git a/Engine/source/BadBehavior/tools/BTUndoActions.cpp b/Engine/source/BadBehavior/tools/BTUndoActions.cpp new file mode 100644 index 0000000000..8358f5e8ef --- /dev/null +++ b/Engine/source/BadBehavior/tools/BTUndoActions.cpp @@ -0,0 +1,134 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2014 Guy Allard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//----------------------------------------------------------------------------- + +#include "BTUndoActions.h" +#include "console/consoleTypes.h" +#include "console/simSet.h" + +S32 getNextObjectInGroup(SimObject *object, SimGroup *group) +{ + group->lock(); + S32 nextId = -1; + + if(object != group->last() && group->find( group->begin(), group->end(), object ) != group->end()) + { + for( SimSet::iterator i = group->begin(); i != group->end(); i++) + { + if( *i == object ) + { + nextId = (*++i)->getId(); + break; + } + } + group->unlock(); + } + + return nextId; +} + +IMPLEMENT_CONOBJECT( BTDeleteUndoAction ); + +ConsoleDocClass( BTDeleteUndoAction, + "@brief Behavior Tree Editor delete undo instance\n\n" + "Not intended for game development, for editors or internal use only.\n\n " + "@internal"); + +BTDeleteUndoAction::BTDeleteUndoAction( const UTF8 *actionName ) + : UndoAction( actionName ) +{ +} + +BTDeleteUndoAction::~BTDeleteUndoAction() +{ +} + +void BTDeleteUndoAction::initPersistFields() +{ + Parent::initPersistFields(); +} + +void BTDeleteUndoAction::deleteObject( SimObject *object ) +{ + AssertFatal( object, "BTDeleteUndoAction::deleteObject() - Got null object!" ); + AssertFatal( object->isProperlyAdded(), + "BTDeleteUndoAction::deleteObject() - Object should be registered!" ); + + // Capture the object id. + mObject.id = object->getId(); + + // Save the state. + mObject.memento.save( object ); + + // Store the group. + SimGroup *group = object->getGroup(); + if ( group ) + { + mObject.groupId = group->getId(); + + // and the next object in the group + mObject.nextId = getNextObjectInGroup(object, group); + } + + // Now delete the object. + object->deleteObject(); +} + +DefineEngineMethod( BTDeleteUndoAction, deleteObject, void, ( SimObject* obj ),, "") +{ + if (obj != NULL) { + object->deleteObject(obj); + } +} + +void BTDeleteUndoAction::undo() +{ + // Create the object. + SimObject::setForcedId(mObject.id); // Restore the object's Id + SimObject *object = mObject.memento.restore(); + if ( !object ) + return; + + // Now restore its group. + SimGroup *group; + if ( Sim::findObject( mObject.groupId, group ) ) + { + group->addObject( object ); + + // restore its position in the group + SimObject *nextObj; + if ( Sim::findObject( mObject.nextId, nextObj ) ) + { + group->reOrder(object, nextObj); + } + } + + Con::executef( this, "onUndone" ); +} + +void BTDeleteUndoAction::redo() +{ + SimObject *object = Sim::findObject( mObject.id ); + if ( object ) + object->deleteObject(); + + Con::executef( this, "onRedone" ); +} diff --git a/Engine/source/BadBehavior/tools/BTUndoActions.h b/Engine/source/BadBehavior/tools/BTUndoActions.h new file mode 100644 index 0000000000..931e53e1ec --- /dev/null +++ b/Engine/source/BadBehavior/tools/BTUndoActions.h @@ -0,0 +1,77 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2014 Guy Allard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//----------------------------------------------------------------------------- + +#ifndef _BT_UNDO_ACTIONS_H_ +#define _BT_UNDO_ACTIONS_H_ + +#ifndef _UNDO_H_ +#include "util/undo.h" +#endif +#ifndef _CONSOLE_SIMOBJECTMEMENTO_H_ +#include "console/simObjectMemento.h" +#endif + +S32 getNextObjectInSet(SimObject *obj, SimGroup &group); + +class BTDeleteUndoAction : public UndoAction +{ + typedef UndoAction Parent; + +protected: + + struct ObjectState + { + /// The object we deleted and will restore in undo. + SimObjectId id; + + /// The captured object state. + SimObjectMemento memento; + + /// Keep track of the parent group. + SimObjectId groupId; + + /// Keep track of the position within the parent group + SimObjectId nextId; + }; + + /// The object we're deleting. + ObjectState mObject; + +public: + + DECLARE_CONOBJECT( BTDeleteUndoAction ); + static void initPersistFields(); + + BTDeleteUndoAction( const UTF8* actionName = "Delete Object" ); + virtual ~BTDeleteUndoAction(); + + /// + void deleteObject( SimObject *object ); + + // UndoAction + virtual void undo(); + virtual void redo(); +}; + + + +#endif // _BT_UNDO_ACTIONS_H_ \ No newline at end of file diff --git a/Engine/source/BadBehavior/tools/guiBTViewCtrl.cpp b/Engine/source/BadBehavior/tools/guiBTViewCtrl.cpp new file mode 100644 index 0000000000..812d3eec54 --- /dev/null +++ b/Engine/source/BadBehavior/tools/guiBTViewCtrl.cpp @@ -0,0 +1,301 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2014 Guy Allard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//----------------------------------------------------------------------------- + +#include "gfx/gfxDrawUtil.h" +#include "gui/worldEditor/editorIconRegistry.h" +#include "gui/controls/guiTextEditCtrl.h" + +#include "guiBTViewCtrl.h" + +using namespace BadBehavior; + +IMPLEMENT_CONOBJECT(GuiBehaviorTreeViewCtrl); + +void GuiBehaviorTreeViewCtrl::onRenderCell(Point2I offset, Point2I cell, bool, bool ) +{ + if( !mVisibleItems.size() ) + return; + + // Do some sanity checking and data retrieval. + AssertFatal(cell.y < mVisibleItems.size(), "GuiTreeViewCtrl::onRenderCell: invalid cell"); + Item * item = mVisibleItems[cell.y]; + + // If there's no object, deal with it. + if(item->isInspectorData()) + if(!item->getObject()) + return; + + RectI drawRect( offset, mCellSize ); + GFXDrawUtil *drawer = GFX->getDrawUtil(); + drawer->clearBitmapModulation(); + + FrameAllocatorMarker txtBuff; + + // Ok, we have the item. There are a few possibilities at this point: + // - We need to draw inheritance lines and a treeview-chosen icon + // OR + // - We have to draw an item-dependent icon + // - If we're mouseover, we have to highlight it. + // + // - We have to draw the text for the item + // - Taking into account various mouseover states + // - Taking into account the value (set or not) + // - If it's an inspector data, we have to do some custom rendering + // - ADDED: If it is being renamed, we also have custom rendering. + + // Ok, first draw the tab and icon. + + // Do we draw the tree lines? + if( mFlags.test(ShowTreeLines) ) + { + drawRect.point.x += ( mTabSize * item->mTabLevel ); + Item* parent = item->mParent; + for ( S32 i = item->mTabLevel; ( parent && i > 0 ); i-- ) + { + drawRect.point.x -= mTabSize; + if ( parent->mNext ) + drawer->drawBitmapSR( mProfile->mTextureObject, drawRect.point, mProfile->mBitmapArrayRects[BmpLine] ); + + parent = parent->mParent; + } + } + + // Now, the icon... + drawRect.point.x = offset.x + mTabSize * item->mTabLevel; + + // First, draw the rollover glow, if it's an inner node. + if ( item->isParent() && item->mState.test( Item::MouseOverBmp ) ) + drawer->drawBitmapSR( mProfile->mTextureObject, drawRect.point, mProfile->mBitmapArrayRects[BmpGlow] ); + + // Now, do we draw a treeview-selected item or an item dependent one? + S32 newOffset = 0; // This is stored so we can render glow, then update render pos. + + S32 bitmap = 0; + + // Ok, draw the treeview lines as appropriate. + + bool drawBitmap = true; + if ( !item->isParent() ) + { + if( mFlags.test( ShowTreeLines ) ) + { + if( ( item->mNext && item->mPrevious ) + || ( item->mNext && item->mParent && ( !_isRootLevelItem( item ) || mShowRoot ) ) ) + bitmap = BmpChild; + else if( item->mNext && ( !item->mParent || !mShowRoot ) ) + bitmap = BmpFirstChild; + else if( item->mPrevious || ( item->mParent && !_isRootLevelItem( item ) ) ) + bitmap = BmpLastChild; + else + drawBitmap = false; + } + else + drawBitmap = false; + } + else + { + bitmap = item->isExpanded() ? BmpExp : BmpCon; + + if( mFlags.test( ShowTreeLines ) ) + { + // Shift indices to show versions with tree lines. + + if ( item->mParent || item->mPrevious ) + bitmap += ( item->mNext ? 3 : 2 ); + else + bitmap += ( item->mNext ? 1 : 0 ); + } + } + + if( ( bitmap >= 0 ) && ( bitmap < mProfile->mBitmapArrayRects.size() ) ) + { + if( drawBitmap ) + drawer->drawBitmapSR( mProfile->mTextureObject, drawRect.point, mProfile->mBitmapArrayRects[bitmap] ); + newOffset = mProfile->mBitmapArrayRects[bitmap].extent.x; + } + + if(item->isInspectorData()) + { + // draw lock icon if need be + S32 icon = Lock1; + S32 icon2 = Hidden; + + if (item->getObject() && item->getObject()->isLocked()) + { + if (mIconTable[icon]) + { + //drawRect.point.x = offset.x + mTabSize * item->mTabLevel + mIconTable[icon].getWidth(); + drawRect.point.x += mIconTable[icon].getWidth(); + drawer->drawBitmap( mIconTable[icon], drawRect.point ); + } + } + + if (item->getObject() && item->getObject()->isHidden()) + { + if (mIconTable[icon2]) + { + //drawRect.point.x = offset.x + mTabSize * item->mTabLevel + mIconTable[icon].getWidth(); + drawRect.point.x += mIconTable[icon2].getWidth(); + drawer->drawBitmap( mIconTable[icon2], drawRect.point ); + } + } + + /*SimObject * pObject = item->getObject(); + SimGroup * pGroup = ( pObject == NULL ) ? NULL : dynamic_cast( pObject ); + + // If this item is a VirtualParent we can use the generic SimGroup123 icons. + // However if there is already an icon in the EditorIconRegistry for this + // exact class (not counting parent class icons) we want to use that instead. + bool hasClassIcon = gEditorIcons.hasIconNoRecurse( pObject ); + + // draw the icon associated with the item + if ( !hasClassIcon && item->mState.test(Item::VirtualParent)) + { + if ( pGroup != NULL) + { + if (item->isExpanded()) + item->mIcon = SimGroup1; + else + item->mIcon = SimGroup2; + } + else + item->mIcon = SimGroup2; + } + + if ( !hasClassIcon && item->mState.test(Item::Marked)) + { + if (item->isInspectorData()) + { + if ( pGroup != NULL ) + { + if (item->isExpanded()) + item->mIcon = SimGroup3; + else + item->mIcon = SimGroup4; + } + } + }*/ + + GFXTexHandle iconHandle; + + if ( ( item->mIcon != -1 ) && mIconTable[item->mIcon] ) + iconHandle = mIconTable[item->mIcon]; +#ifdef TORQUE_TOOLS + else + iconHandle = gEditorIcons.findIcon( item->getObject() ); +#endif + + if ( iconHandle.isValid() ) + { + S32 iconHeight = (mItemHeight - iconHandle.getHeight()) / 2; + S32 oldHeight = drawRect.point.y; + if(iconHeight > 0) + drawRect.point.y += iconHeight; + drawRect.point.x += iconHandle.getWidth(); + drawer->drawBitmap( iconHandle, drawRect.point ); + drawRect.point.y = oldHeight; + } + } + else + { + S32 icon = item->isExpanded() ? item->mScriptInfo.mExpandedImage : item->mScriptInfo.mNormalImage; + if ( icon ) + { + if (mIconTable[icon]) + { + S32 iconHeight = (mItemHeight - mIconTable[icon].getHeight()) / 2; + S32 oldHeight = drawRect.point.y; + if(iconHeight > 0) + drawRect.point.y += iconHeight; + drawRect.point.x += mIconTable[icon].getWidth(); + drawer->drawBitmap( mIconTable[icon], drawRect.point ); + drawRect.point.y = oldHeight; + } + } + } + + // Ok, update offset so we can render some text! + drawRect.point.x += newOffset; + + // Ok, now we're off to rendering the actual data for the treeview item. + + U32 bufLen = 1024; //item->mDataRenderWidth + 1; + char *displayText = (char *)txtBuff.alloc(bufLen); + displayText[bufLen-1] = 0; + item->getDisplayText(bufLen, displayText); + + // Draw the rollover/selected bitmap, if one was specified. + drawRect.extent.x = mProfile->mFont->getStrWidth( displayText ) + ( 2 * mTextOffset ); + if ( item->mState.test( Item::Selected ) && mTexSelected ) + drawer->drawBitmapStretch( mTexSelected, drawRect ); + else if ( item->mState.test( Item::MouseOverText ) && mTexRollover ) + drawer->drawBitmapStretch( mTexRollover, drawRect ); + + // Offset a bit so as to space text properly. + drawRect.point.x += mTextOffset; + + // Determine what color the font should be. + ColorI fontColor; + + fontColor = item->mState.test( Item::Selected ) ? mProfile->mFontColorSEL : + ( item->mState.test( Item::MouseOverText ) ? mProfile->mFontColorHL : mProfile->mFontColor ); + + if (item->mState.test(Item::Selected)) + { + drawer->drawRectFill(drawRect, mProfile->mFillColorSEL); + } + else if (item->mState.test(Item::MouseOverText)) + { + drawer->drawRectFill(drawRect, mProfile->mFillColorHL); + } + + if( item->mState.test(Item::MouseOverText) ) + { + fontColor = mProfile->mFontColorHL; + } + + drawer->setBitmapModulation( fontColor ); + + // Center the text horizontally. + S32 height = (mItemHeight - mProfile->mFont->getHeight()) / 2; + + if(height > 0) + drawRect.point.y += height; + + // JDD - offset by two pixels or so to keep the text from rendering RIGHT ONTOP of the outline + drawRect.point.x += 2; + + drawer->drawText( mProfile->mFont, drawRect.point, displayText, mProfile->mFontColors ); + + if ( mRenamingItem == item && mRenameCtrl ) + { + Point2I ctrPos = globalToLocalCoord( drawRect.point ); + ctrPos.y -= height; + ctrPos.x -= 2; + + Point2I ctrExtent( getWidth() - ctrPos.x, drawRect.extent.y ); + + mRenameCtrl->setPosition( ctrPos ); + mRenameCtrl->setExtent( ctrExtent ); + mRenameCtrl->setVisible( true ); + } +} \ No newline at end of file diff --git a/Engine/source/BadBehavior/tools/guiBTViewCtrl.h b/Engine/source/BadBehavior/tools/guiBTViewCtrl.h new file mode 100644 index 0000000000..9800f2d679 --- /dev/null +++ b/Engine/source/BadBehavior/tools/guiBTViewCtrl.h @@ -0,0 +1,45 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2014 Guy Allard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//----------------------------------------------------------------------------- + +#ifndef _GUI_BTVIEWCTRL_H_ +#define _GUI_BTVIEWCTRL_H_ + +#ifndef _GUI_TREEVIEWCTRL_H +#include "gui/controls/guiTreeViewCtrl.h" +#endif + +namespace BadBehavior +{ + // subclassing GuiTreeViewCtrl so that we can tweak it for the behavior tree editor + class GuiBehaviorTreeViewCtrl : public GuiTreeViewCtrl + { + typedef GuiTreeViewCtrl Parent; + + public: + void onRenderCell(Point2I offset, Point2I cell, bool, bool); + + DECLARE_CONOBJECT(GuiBehaviorTreeViewCtrl); + }; + +} // namespace BadBehavior + +#endif diff --git a/Templates/BaseGame/game/tools/behaviorTreeEditor/gui/BTEditorCreatePrompt.ed.gui b/Templates/BaseGame/game/tools/behaviorTreeEditor/gui/BTEditorCreatePrompt.ed.gui new file mode 100644 index 0000000000..ceefffe00b --- /dev/null +++ b/Templates/BaseGame/game/tools/behaviorTreeEditor/gui/BTEditorCreatePrompt.ed.gui @@ -0,0 +1,217 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2014 Guy Allard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//----------------------------------------------------------------------------- + +//--- OBJECT WRITE BEGIN --- +%guiContent = new GuiControl(BTEditorCreatePrompt,EditorGuiGroup) { + position = "0 0"; + extent = "1024 768"; + minExtent = "8 2"; + horizSizing = "right"; + vertSizing = "bottom"; + profile = "ToolsGuiDefaultProfile"; + visible = "1"; + active = "1"; + tooltipProfile = "ToolsGuiToolTipProfile"; + hovertime = "1000"; + isContainer = "1"; + canSave = "1"; + canSaveDynamicFields = "1"; + fixedAspectRatio = "0"; + + new GuiWindowCtrl() { + text = "Create New Tree"; + resizeWidth = "0"; + resizeHeight = "0"; + canMove = "0"; + canClose = "1"; + canMinimize = "0"; + canMaximize = "0"; + canCollapse = "0"; + closeCommand = "canvas.popDialog(DatablockEditorCreatePrompt);"; + edgeSnap = "0"; + margin = "0 0 0 0"; + padding = "0 0 0 0"; + anchorTop = "1"; + anchorBottom = "0"; + anchorLeft = "1"; + anchorRight = "0"; + position = "389 252"; + extent = "207 145"; + minExtent = "8 2"; + horizSizing = "right"; + vertSizing = "bottom"; + profile = "ToolsGuiWindowProfile"; + visible = "1"; + active = "1"; + tooltipProfile = "ToolsGuiToolTipProfile"; + hovertime = "1000"; + isContainer = "1"; + canSave = "1"; + canSaveDynamicFields = "0"; + + new GuiTextCtrl() { + text = "Your new tree must have a name"; + maxLength = "1024"; + margin = "0 0 0 0"; + padding = "0 0 0 0"; + anchorTop = "1"; + anchorBottom = "0"; + anchorLeft = "1"; + anchorRight = "0"; + position = "7 26"; + extent = "190 15"; + minExtent = "8 2"; + horizSizing = "right"; + vertSizing = "bottom"; + profile = "ToolsGuiTextProfile"; + visible = "1"; + active = "1"; + tooltipProfile = "ToolsGuiToolTipProfile"; + hovertime = "1000"; + isContainer = "0"; + canSave = "1"; + canSaveDynamicFields = "0"; + }; + new GuiTextEditCtrl() { + historySize = "0"; + tabComplete = "0"; + sinkAllKeyEvents = "0"; + password = "0"; + passwordMask = "*"; + text = "Name"; + maxLength = "1024"; + margin = "0 0 0 0"; + padding = "0 0 0 0"; + anchorTop = "1"; + anchorBottom = "0"; + anchorLeft = "1"; + anchorRight = "0"; + position = "7 45"; + extent = "191 18"; + minExtent = "8 2"; + horizSizing = "right"; + vertSizing = "bottom"; + profile = "ToolsGuiTextEditProfile"; + visible = "1"; + active = "1"; + tooltipProfile = "ToolsGuiToolTipProfile"; + hovertime = "1000"; + isContainer = "0"; + internalName = "CreateTreeName"; + canSave = "1"; + canSaveDynamicFields = "0"; + }; + new GuiButtonCtrl() { + text = "Create"; + groupNum = "-1"; + buttonType = "PushButton"; + useMouseEvents = "0"; + position = "7 114"; + extent = "122 22"; + minExtent = "8 2"; + horizSizing = "right"; + vertSizing = "bottom"; + profile = "ToolsGuiButtonProfile"; + visible = "1"; + active = "1"; + Command = "BTEditor.createPromptNameCheck();"; + accelerator = "return"; + tooltipProfile = "ToolsGuiToolTipProfile"; + hovertime = "1000"; + isContainer = "0"; + canSave = "1"; + canSaveDynamicFields = "0"; + }; + new GuiButtonCtrl() { + text = "Cancel"; + groupNum = "-1"; + buttonType = "PushButton"; + useMouseEvents = "0"; + position = "135 114"; + extent = "63 22"; + minExtent = "8 2"; + horizSizing = "right"; + vertSizing = "bottom"; + profile = "ToolsGuiButtonProfile"; + visible = "1"; + active = "1"; + Command = "canvas.popDialog(BTEditorCreatePrompt);"; + accelerator = "escape"; + tooltipProfile = "ToolsGuiToolTipProfile"; + hovertime = "1000"; + isContainer = "0"; + canSave = "1"; + canSaveDynamicFields = "0"; + }; + new GuiTextCtrl() { + text = "Copy tree from"; + maxLength = "1024"; + margin = "0 0 0 0"; + padding = "0 0 0 0"; + anchorTop = "1"; + anchorBottom = "0"; + anchorLeft = "1"; + anchorRight = "0"; + position = "7 66"; + extent = "100 17"; + minExtent = "8 2"; + horizSizing = "right"; + vertSizing = "bottom"; + profile = "ToolsGuiTextProfile"; + visible = "1"; + active = "1"; + tooltipProfile = "ToolsGuiToolTipProfile"; + hovertime = "1000"; + isContainer = "0"; + canSave = "1"; + canSaveDynamicFields = "0"; + }; + new GuiPopUpMenuCtrl() { + maxPopupHeight = "200"; + sbUsesNAColor = "0"; + reverseTextList = "0"; + bitmapBounds = "16 16"; + maxLength = "1024"; + margin = "0 0 0 0"; + padding = "0 0 0 0"; + anchorTop = "1"; + anchorBottom = "0"; + anchorLeft = "1"; + anchorRight = "0"; + position = "7 87"; + extent = "191 19"; + minExtent = "8 2"; + horizSizing = "right"; + vertSizing = "bottom"; + profile = "GuiPopUpMenuProfile"; + visible = "1"; + active = "1"; + tooltipProfile = "ToolsGuiToolTipProfile"; + hovertime = "1000"; + isContainer = "0"; + internalName = "CopySourceDropdown"; + canSave = "1"; + canSaveDynamicFields = "0"; + }; + }; +}; +//--- OBJECT WRITE END --- diff --git a/Templates/BaseGame/game/tools/behaviorTreeEditor/gui/behaviorTreeEditor.ed.gui b/Templates/BaseGame/game/tools/behaviorTreeEditor/gui/behaviorTreeEditor.ed.gui new file mode 100644 index 0000000000..5e9bbd4061 --- /dev/null +++ b/Templates/BaseGame/game/tools/behaviorTreeEditor/gui/behaviorTreeEditor.ed.gui @@ -0,0 +1,321 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2014 Guy Allard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//----------------------------------------------------------------------------- + +//--- OBJECT WRITE BEGIN --- +%guiContent = new GuiControl(BTEditor) { + position = "0 0"; + extent = "1024 768"; + minExtent = "8 2"; + horizSizing = "width"; + vertSizing = "height"; + profile = "ToolsGuiDefaultProfile"; + visible = "1"; + active = "1"; + tooltipProfile = "GuiToolTipProfile"; + hovertime = "1000"; + isContainer = "1"; + canSave = "1"; + canSaveDynamicFields = "1"; + lastContent = "1663"; + + new GuiControl() { + position = "0 0"; + extent = "1024 32"; + minExtent = "8 2"; + horizSizing = "width"; + vertSizing = "bottom"; + profile = "ToolsGuiDefaultProfile"; + visible = "1"; + active = "1"; + tooltipProfile = "GuiToolTipProfile"; + hovertime = "1000"; + isContainer = "1"; + internalName = "top bar"; + canSave = "1"; + canSaveDynamicFields = "0"; + + new GuiContainer(BTToolBar) { + margin = "0 0 0 0"; + padding = "0 0 0 0"; + anchorTop = "1"; + anchorBottom = "0"; + anchorLeft = "1"; + anchorRight = "0"; + position = "0 0"; + extent = "16000 32"; + minExtent = "8 2"; + horizSizing = "right"; + vertSizing = "bottom"; + profile = "menubarProfile"; + visible = "1"; + active = "1"; + tooltipProfile = "ToolsGuiToolTipProfile"; + hovertime = "1000"; + isContainer = "1"; + canSave = "1"; + canSaveDynamicFields = "0"; + + new GuiControl() { + position = "5 0"; + extent = "695 32"; + minExtent = "8 2"; + horizSizing = "width"; + vertSizing = "bottom"; + profile = "ToolsGuiDefaultProfile"; + visible = "1"; + active = "1"; + tooltipProfile = "ToolsGuiToolTipProfile"; + hovertime = "1000"; + isContainer = "1"; + canSave = "1"; + canSaveDynamicFields = "0"; + + new GuiPopUpMenuCtrl(BTEditorContentList) { + maxPopupHeight = "200"; + sbUsesNAColor = "0"; + reverseTextList = "0"; + bitmapBounds = "16 16"; + text = "BotBehavior"; + maxLength = "1024"; + margin = "0 0 0 0"; + padding = "0 0 0 0"; + anchorTop = "1"; + anchorBottom = "0"; + anchorLeft = "1"; + anchorRight = "0"; + position = "8 7"; + extent = "145 18"; + minExtent = "8 2"; + horizSizing = "right"; + vertSizing = "bottom"; + profile = "ToolsGuiPopUpMenuProfile"; + visible = "1"; + active = "1"; + tooltipProfile = "ToolsGuiToolTipProfile"; + hovertime = "1000"; + isContainer = "0"; + canSave = "1"; + canSaveDynamicFields = "0"; + }; + }; + }; + }; + new GuiControl() { + position = "0 32"; + extent = "1024 719"; + minExtent = "8 2"; + horizSizing = "width"; + vertSizing = "height"; + profile = "ToolsGuiDefaultProfile"; + visible = "1"; + active = "1"; + tooltipProfile = "GuiToolTipProfile"; + hovertime = "1000"; + isContainer = "1"; + internalName = "middle bar"; + canSave = "1"; + canSaveDynamicFields = "0"; + + new GuiFrameSetCtrl() { + columns = "0 514"; + rows = "0"; + borderWidth = "4"; + borderColor = "48 32 48 32"; + borderEnable = "alwaysOn"; + borderMovable = "alwaysOn"; + autoBalance = "0"; + fudgeFactor = "0"; + docking = "None"; + margin = "0 0 0 0"; + padding = "0 0 0 0"; + anchorTop = "1"; + anchorBottom = "0"; + anchorLeft = "1"; + anchorRight = "0"; + position = "0 0"; + extent = "1024 719"; + minExtent = "8 2"; + horizSizing = "width"; + vertSizing = "height"; + profile = "ToolsGuiFrameSetProfile"; + visible = "1"; + active = "1"; + tooltipProfile = "GuiToolTipProfile"; + hovertime = "1000"; + isContainer = "1"; + canSave = "1"; + canSaveDynamicFields = "0"; + + new GuiContainer() { + margin = "0 0 0 0"; + padding = "0 0 0 0"; + anchorTop = "1"; + anchorBottom = "0"; + anchorLeft = "1"; + anchorRight = "0"; + position = "0 0"; + extent = "510 719"; + minExtent = "8 2"; + horizSizing = "width"; + vertSizing = "height"; + profile = "GuiDefaultProfile"; + visible = "1"; + active = "1"; + tooltipProfile = "GuiToolTipProfile"; + hovertime = "1000"; + isContainer = "1"; + canSave = "1"; + canSaveDynamicFields = "0"; + + new GuiTabBookCtrl(BTEditorTabBook) { + tabPosition = "Top"; + tabMargin = "7"; + minTabWidth = "48"; + tabHeight = "20"; + allowReorder = "1"; + defaultPage = "0"; + selectedPage = "0"; + frontTabPadding = "0"; + docking = "Client"; + margin = "0 0 0 0"; + padding = "0 0 0 0"; + anchorTop = "1"; + anchorBottom = "0"; + anchorLeft = "1"; + anchorRight = "0"; + position = "0 0"; + extent = "510 719"; + minExtent = "8 2"; + horizSizing = "width"; + vertSizing = "height"; + profile = "ToolsGuiTabBookProfile"; + visible = "1"; + active = "1"; + tooltipProfile = "GuiToolTipProfile"; + hovertime = "1000"; + isContainer = "1"; + canSave = "1"; + canSaveDynamicFields = "0"; + + }; + }; + new GuiScrollCtrl() { + willFirstRespond = "1"; + hScrollBar = "alwaysOn"; + vScrollBar = "alwaysOn"; + lockHorizScroll = "0"; + lockVertScroll = "0"; + constantThumbHeight = "0"; + childMargin = "0 0"; + mouseWheelScrollSpeed = "-1"; + margin = "0 0 0 0"; + padding = "0 0 0 0"; + anchorTop = "1"; + anchorBottom = "0"; + anchorLeft = "1"; + anchorRight = "0"; + position = "514 0"; + extent = "510 719"; + minExtent = "8 2"; + horizSizing = "right"; + vertSizing = "bottom"; + profile = "ToolsGuiScrollProfile"; + visible = "1"; + active = "1"; + tooltipProfile = "GuiToolTipProfile"; + hovertime = "1000"; + isContainer = "1"; + canSave = "1"; + canSaveDynamicFields = "0"; + + new GuiInspector(BehaviorTreeInspector) { + dividerMargin = "5"; + showCustomFields = "1"; + stackingType = "Vertical"; + horizStacking = "Left to Right"; + vertStacking = "Top to Bottom"; + padding = "1"; + dynamicSize = "1"; + dynamicNonStackExtent = "0"; + dynamicPos = "0"; + changeChildSizeToFit = "1"; + changeChildPosition = "1"; + position = "1 1"; + extent = "493 328"; + minExtent = "16 16"; + horizSizing = "right"; + vertSizing = "bottom"; + profile = "GuiInspectorProfile"; + visible = "1"; + active = "1"; + tooltipProfile = "GuiToolTipProfile"; + hovertime = "1000"; + isContainer = "1"; + canSave = "1"; + canSaveDynamicFields = "0"; + + }; + }; + }; + }; + new GuiControl() { + position = "0 751"; + extent = "1024 17"; + minExtent = "8 2"; + horizSizing = "width"; + vertSizing = "top"; + profile = "menubarProfile"; + visible = "1"; + active = "1"; + tooltipProfile = "GuiToolTipProfile"; + hovertime = "1000"; + isContainer = "1"; + internalName = "bottom bar"; + canSave = "1"; + canSaveDynamicFields = "0"; + + new GuiTextCtrl(BTEditorStatusBar) { + text = "Status"; + maxLength = "1024"; + margin = "0 0 0 0"; + padding = "0 0 0 0"; + anchorTop = "1"; + anchorBottom = "0"; + anchorLeft = "1"; + anchorRight = "0"; + position = "5 0"; + extent = "1019 17"; + minExtent = "64 17"; + horizSizing = "right"; + vertSizing = "bottom"; + profile = "ToolsGuiTextProfile"; + visible = "1"; + active = "1"; + tooltipProfile = "GuiToolTipProfile"; + hovertime = "1000"; + isContainer = "1"; + canSave = "1"; + canSaveDynamicFields = "0"; + }; + }; +}; +//--- OBJECT WRITE END --- diff --git a/Templates/BaseGame/game/tools/behaviorTreeEditor/images/BehaviorTreeView.png b/Templates/BaseGame/game/tools/behaviorTreeEditor/images/BehaviorTreeView.png new file mode 100644 index 0000000000..8315d7137d Binary files /dev/null and b/Templates/BaseGame/game/tools/behaviorTreeEditor/images/BehaviorTreeView.png differ diff --git a/Templates/BaseGame/game/tools/behaviorTreeEditor/images/classIcons/ActiveSelector.png b/Templates/BaseGame/game/tools/behaviorTreeEditor/images/classIcons/ActiveSelector.png new file mode 100644 index 0000000000..c5d2010027 Binary files /dev/null and b/Templates/BaseGame/game/tools/behaviorTreeEditor/images/classIcons/ActiveSelector.png differ diff --git a/Templates/BaseGame/game/tools/behaviorTreeEditor/images/classIcons/Behavior.png b/Templates/BaseGame/game/tools/behaviorTreeEditor/images/classIcons/Behavior.png new file mode 100644 index 0000000000..c391b6ad0f Binary files /dev/null and b/Templates/BaseGame/game/tools/behaviorTreeEditor/images/classIcons/Behavior.png differ diff --git a/Templates/BaseGame/game/tools/behaviorTreeEditor/images/classIcons/FailAlways.png b/Templates/BaseGame/game/tools/behaviorTreeEditor/images/classIcons/FailAlways.png new file mode 100644 index 0000000000..01f998448f Binary files /dev/null and b/Templates/BaseGame/game/tools/behaviorTreeEditor/images/classIcons/FailAlways.png differ diff --git a/Templates/BaseGame/game/tools/behaviorTreeEditor/images/classIcons/Inverter.png b/Templates/BaseGame/game/tools/behaviorTreeEditor/images/classIcons/Inverter.png new file mode 100644 index 0000000000..88578b2b85 Binary files /dev/null and b/Templates/BaseGame/game/tools/behaviorTreeEditor/images/classIcons/Inverter.png differ diff --git a/Templates/BaseGame/game/tools/behaviorTreeEditor/images/classIcons/Loop.png b/Templates/BaseGame/game/tools/behaviorTreeEditor/images/classIcons/Loop.png new file mode 100644 index 0000000000..bc7f5c54b0 Binary files /dev/null and b/Templates/BaseGame/game/tools/behaviorTreeEditor/images/classIcons/Loop.png differ diff --git a/Templates/BaseGame/game/tools/behaviorTreeEditor/images/classIcons/Monitor.png b/Templates/BaseGame/game/tools/behaviorTreeEditor/images/classIcons/Monitor.png new file mode 100644 index 0000000000..83914b53aa Binary files /dev/null and b/Templates/BaseGame/game/tools/behaviorTreeEditor/images/classIcons/Monitor.png differ diff --git a/Templates/BaseGame/game/tools/behaviorTreeEditor/images/classIcons/Parallel.png b/Templates/BaseGame/game/tools/behaviorTreeEditor/images/classIcons/Parallel.png new file mode 100644 index 0000000000..d33c5cebd7 Binary files /dev/null and b/Templates/BaseGame/game/tools/behaviorTreeEditor/images/classIcons/Parallel.png differ diff --git a/Templates/BaseGame/game/tools/behaviorTreeEditor/images/classIcons/RandomSelector.png b/Templates/BaseGame/game/tools/behaviorTreeEditor/images/classIcons/RandomSelector.png new file mode 100644 index 0000000000..f2c366c60f Binary files /dev/null and b/Templates/BaseGame/game/tools/behaviorTreeEditor/images/classIcons/RandomSelector.png differ diff --git a/Templates/BaseGame/game/tools/behaviorTreeEditor/images/classIcons/RandomWait.png b/Templates/BaseGame/game/tools/behaviorTreeEditor/images/classIcons/RandomWait.png new file mode 100644 index 0000000000..460f613537 Binary files /dev/null and b/Templates/BaseGame/game/tools/behaviorTreeEditor/images/classIcons/RandomWait.png differ diff --git a/Templates/BaseGame/game/tools/behaviorTreeEditor/images/classIcons/Root.png b/Templates/BaseGame/game/tools/behaviorTreeEditor/images/classIcons/Root.png new file mode 100644 index 0000000000..62b0c0739c Binary files /dev/null and b/Templates/BaseGame/game/tools/behaviorTreeEditor/images/classIcons/Root.png differ diff --git a/Templates/BaseGame/game/tools/behaviorTreeEditor/images/classIcons/ScriptEval.png b/Templates/BaseGame/game/tools/behaviorTreeEditor/images/classIcons/ScriptEval.png new file mode 100644 index 0000000000..fe084638bc Binary files /dev/null and b/Templates/BaseGame/game/tools/behaviorTreeEditor/images/classIcons/ScriptEval.png differ diff --git a/Templates/BaseGame/game/tools/behaviorTreeEditor/images/classIcons/ScriptFunc.png b/Templates/BaseGame/game/tools/behaviorTreeEditor/images/classIcons/ScriptFunc.png new file mode 100644 index 0000000000..e2e2cac37a Binary files /dev/null and b/Templates/BaseGame/game/tools/behaviorTreeEditor/images/classIcons/ScriptFunc.png differ diff --git a/Templates/BaseGame/game/tools/behaviorTreeEditor/images/classIcons/ScriptedBehavior.png b/Templates/BaseGame/game/tools/behaviorTreeEditor/images/classIcons/ScriptedBehavior.png new file mode 100644 index 0000000000..64a3ada632 Binary files /dev/null and b/Templates/BaseGame/game/tools/behaviorTreeEditor/images/classIcons/ScriptedBehavior.png differ diff --git a/Templates/BaseGame/game/tools/behaviorTreeEditor/images/classIcons/Selector.png b/Templates/BaseGame/game/tools/behaviorTreeEditor/images/classIcons/Selector.png new file mode 100644 index 0000000000..3b20588393 Binary files /dev/null and b/Templates/BaseGame/game/tools/behaviorTreeEditor/images/classIcons/Selector.png differ diff --git a/Templates/BaseGame/game/tools/behaviorTreeEditor/images/classIcons/Sequence.png b/Templates/BaseGame/game/tools/behaviorTreeEditor/images/classIcons/Sequence.png new file mode 100644 index 0000000000..d7006db7b5 Binary files /dev/null and b/Templates/BaseGame/game/tools/behaviorTreeEditor/images/classIcons/Sequence.png differ diff --git a/Templates/BaseGame/game/tools/behaviorTreeEditor/images/classIcons/SubTree.png b/Templates/BaseGame/game/tools/behaviorTreeEditor/images/classIcons/SubTree.png new file mode 100644 index 0000000000..18e3f4c3f1 Binary files /dev/null and b/Templates/BaseGame/game/tools/behaviorTreeEditor/images/classIcons/SubTree.png differ diff --git a/Templates/BaseGame/game/tools/behaviorTreeEditor/images/classIcons/SucceedAlways.png b/Templates/BaseGame/game/tools/behaviorTreeEditor/images/classIcons/SucceedAlways.png new file mode 100644 index 0000000000..da6ed8621f Binary files /dev/null and b/Templates/BaseGame/game/tools/behaviorTreeEditor/images/classIcons/SucceedAlways.png differ diff --git a/Templates/BaseGame/game/tools/behaviorTreeEditor/images/classIcons/Ticker.png b/Templates/BaseGame/game/tools/behaviorTreeEditor/images/classIcons/Ticker.png new file mode 100644 index 0000000000..4d2dbc987f Binary files /dev/null and b/Templates/BaseGame/game/tools/behaviorTreeEditor/images/classIcons/Ticker.png differ diff --git a/Templates/BaseGame/game/tools/behaviorTreeEditor/images/classIcons/Wait.png b/Templates/BaseGame/game/tools/behaviorTreeEditor/images/classIcons/Wait.png new file mode 100644 index 0000000000..66b01e2180 Binary files /dev/null and b/Templates/BaseGame/game/tools/behaviorTreeEditor/images/classIcons/Wait.png differ diff --git a/Templates/BaseGame/game/tools/behaviorTreeEditor/images/classIcons/WaitForSignal.png b/Templates/BaseGame/game/tools/behaviorTreeEditor/images/classIcons/WaitForSignal.png new file mode 100644 index 0000000000..b3a4fdada3 Binary files /dev/null and b/Templates/BaseGame/game/tools/behaviorTreeEditor/images/classIcons/WaitForSignal.png differ diff --git a/Templates/BaseGame/game/tools/behaviorTreeEditor/main.cs b/Templates/BaseGame/game/tools/behaviorTreeEditor/main.cs new file mode 100644 index 0000000000..1f341789ea --- /dev/null +++ b/Templates/BaseGame/game/tools/behaviorTreeEditor/main.cs @@ -0,0 +1,31 @@ +function reloadBTE() +{ + if(isObject(BehaviorTreeEditorGui)) + BehaviorTreeEditorGui.delete(); + + initializeBehaviorTreeEditor(); +} + +function initializeBehaviorTreeEditor() +{ + echo( " % - Initializing Behavior Tree Editor" ); + + // exec the scripts and gui + exec("./scripts/behaviorTreeEditorProfiles.ed.cs"); + exec("./gui/behaviorTreeEditor.ed.gui"); + exec("./gui/BTEditorCreatePrompt.ed.gui"); + exec("./scripts/guiBehaviorTreeViewCtrl.ed.cs"); + exec("./scripts/behaviorTreeEditorCanvas.ed.cs"); + exec("./scripts/behaviorTreeEditor.ed.cs"); + exec("./scripts/behaviorTreeEditorUndo.ed.cs"); + exec("./scripts/behaviorTreeEditorStatusBar.ed.cs"); + exec("./scripts/behaviorTreeEditorContentList.ed.cs"); + exec("./scripts/behaviorTreeEditorInspector.ed.cs"); + + // register the class icons + EditorIconRegistry::loadFromPath("tools/behaviorTreeEditor/images/classIcons/"); +} + +function destroyBehaviorTreeEditor() +{ +} diff --git a/Templates/BaseGame/game/tools/behaviorTreeEditor/scripts/behaviorTreeEditor.ed.cs b/Templates/BaseGame/game/tools/behaviorTreeEditor/scripts/behaviorTreeEditor.ed.cs new file mode 100644 index 0000000000..10fb9a583e --- /dev/null +++ b/Templates/BaseGame/game/tools/behaviorTreeEditor/scripts/behaviorTreeEditor.ed.cs @@ -0,0 +1,393 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2014 Guy Allard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//----------------------------------------------------------------------------- + +$inBehaviorTreeEditor = false; + +//============================================================================== +// INIT +//============================================================================== +function BTEdit() +{ + if (!$InBehaviorTreeEditor) + { + if(!isObject(BTEditCanvas)) + new GuiControl(BTEditCanvas, EditorGuiGroup); + + BTEditor.startUp(Canvas.getContent()); + + $InBehaviorTreeEditor = true; + BehaviorTreeManager.onBehaviorTreeEditor(true); + } + else + { + BTEditCanvas.quit(); + } +} + +function toggleBehaviorTreeEditor( %make ) +{ + if( %make ) + { + BTEdit(); + cancel($Game::Schedule); + } +} + +GlobalActionMap.bind( keyboard, "Shift F9", toggleBehaviorTreeEditor ); + + +function BTEditor::startUp(%this, %content) +{ + %this.lastContent=%content; + Canvas.setContent( BTEditor ); + //Canavs.pushDialog(BTEditor); + + BadBehaviorMenubar.attachToCanvas(Canvas,0); + + if(!isObject(BehaviorTreeManager)) + // This isn't pretty, but we need to load up existing trees + exec("scripts/server/BadBehavior/behaviorTreeManager.cs"); + + if(BehaviorTreeGroup.getCount() == 0) + { + %this.createTree(); + } + else + { + BTEditorContentList.refresh(); + if(BTEditorTabBook.getCount() == 0) + BTEditorContentList.setFirstSelected(); + } + + %this.updateUndoMenu(); + %this.updateNodeTypes(); +} + + +function BTEditor::updateNodeTypes(%this) +{ + if(isObject(BTNodeTypes)) + BTNodeTypes.delete(); + + new SimSet(BTNodeTypes); + %set = new SimSet() { + internalName = "Composite"; + }; + %set.add( new ScriptObject() { nodeType = "ActiveSelector"; } ); + %set.add( new ScriptObject() { nodeType = "Parallel"; } ); + %set.add( new ScriptObject() { nodeType = "RandomSelector"; } ); + %set.add( new ScriptObject() { nodeType = "Selector"; } ); + %set.add( new ScriptObject() { nodeType = "Sequence"; } ); + BTNodeTypes.add(%set); + + %set = new SimSet() { + internalName = "Decorator"; + }; + %set.add( new ScriptObject() { nodeType = "FailAlways"; }); + %set.add( new ScriptObject() { nodeType = "Inverter"; } ); + %set.add( new ScriptObject() { nodeType = "Loop"; } ); + %set.add( new ScriptObject() { nodeType = "Monitor"; } ); + %set.add( new ScriptObject() { nodeType = "SucceedAlways"; } ); + %set.add( new ScriptObject() { nodeType = "Ticker"; } ); + BTNodeTypes.add(%set); + + %set = new SimSet() { + internalName = "Leaf"; + }; + %set.add( new ScriptObject() { nodeType = "RandomWait"; } ); + %set.add( new ScriptObject() { nodeType = "ScriptedBehavior"; } ); + %set.add( new ScriptObject() { nodeType = "ScriptEval"; } ); + %set.add( new ScriptObject() { nodeType = "ScriptFunc"; } ); + %set.add( new ScriptObject() { nodeType = "SubTree"; } ); + %set.add( new ScriptObject() { nodeType = "Wait"; } ); + %set.add( new ScriptObject() { nodeType = "WaitForSignal"; } ); + BTNodeTypes.add(%set); +} + +function BTEditor::getBaseNodeType(%this, %type) +{ + foreach(%baseType in BTNodeTypes) + { + if(%baseType.internalName $= %type) // supplied basetype + return %type; + + foreach(%derivedType in %baseType) // supplied derived type + { + if(%derivedType.nodeType $= %type) + return %baseType.internalName; + } + } + return ""; +} + +function BTEditor::viewTree(%this, %tree) +{ + %viewPage = -1; + foreach(%page in BTEditorTabBook) + { + if(%page.rootNode == %tree) + { + %viewPage = %page; + break; + } + } + + if(isObject(%viewPage)) + { + %viewPage.select(); + } + else + { + %newPage = BTEditor::newPage(); + %newPage.setText(%tree.name); + %newPage.rootNode = %tree; + BTEditorTabBook.addGuiControl(%newPage); + %newPage-->BTView.open(%tree); + %newPage-->BTView.refresh(); + } + %this.updateUndoMenu(); +} + +function BTEditor::createTree(%this) +{ + %list = BTEditorCreatePrompt-->CopySourceDropdown; + %list.clear(); + foreach (%tree in BehaviorTreeGroup) + %list.add(%tree.getName(), %tree); + Canvas.pushDialog(BTEditorCreatePrompt); +} + +function BTEditor::getCurrentViewCtrl(%this) +{ + %pageId = BTEditorTabBook.getSelectedPage(); + if(%pageId >= 0) + return BTEditorTabBook.getObject(%pageId)-->BTView; + + return %pageId; +} + + +function BTEditor::getTreeRoot(%this, %node) +{ + %current = %node; + while(%current.getClassName() !$= "Root" && isObject(%current)) + { + %current = %current.getGroup(); + } + return %current; +} + +function BTEditor::getCurrentRootNode(%this) +{ + return %this.getCurrentViewCtrl().getRootNode(); +} + + +function BTEditor::createPromptNameCheck(%this) +{ + %name = BTEditorCreatePrompt-->CreateTreeName.getText(); + if( !Editor::validateObjectName( %name, true ) ) + return; + + // Fetch the copy source and clear the list. + + %copySource = BTEditorCreatePrompt-->copySourceDropdown.getText(); + BTEditorCreatePrompt-->copySourceDropdown.clear(); + + // Remove the dialog and create the tree. + + canvas.popDialog( BTEditorCreatePrompt ); + %this.createTreeFinish( %name, %copySource ); +} + +function BTEditor::createTreeFinish( %this, %name, %copySource ) +{ + %newTree = -1; + pushInstantGroup(BehaviorTreeGroup); + if(%copySource !$= "") + { + %newTree = %copySource.deepClone(); + } + else + { + %newTree = new Root(); + } + popInstantGroup(); + + %newTree.setName(%name); + %newTree.setFilename(""); + + BTEditorContentList.refresh(); + BTEditorContentList.setSelected(BTEditorContentList.findText(%newTree.name)); +} + +function BTEditor::saveTree(%this, %tree, %prompt) +{ + // check we actually have something to save + if(!isObject(%tree)) + return; + + if((%file = %tree.getFileName()) !$= "") + { + %path = filePath(%file); + } + else + { + %path = "scripts/server/BadBehavior/behaviorTrees"; + %file = %path @ "/" @ %tree.name; + + if(!isDirectory(%path)) + createPath(%path @ "/"); + + %prompt = true; + } + + if(%prompt || !isFile(%file)) + { + %dlg = new SaveFileDialog() + { + filters = "Torque script files (*.cs)|*.cs|"; + defaultPath = %path; + defaultFile = %file; + changePath = true; + overwritePrompt = true; + }; + + if(%dlg.execute()) + { + %file = %dlg.fileNamfe; + %dlg.delete(); + } + else + { + return; + } + } + + %tree.save(%file); + %tree.setFileName(collapseFilename(%file)); + BTEditorStatusBar.setText("Saved '" @ %tree.name @ "' to file" SPC %tree.getFileName()); +} + +//============================================================================== +// VIEW +//============================================================================== + +function BTEditor::expandAll(%this) +{ + %this.getCurrentViewCtrl().expandAll(); +} + +function BTEditor::collapseAll(%this) +{ + %this.getCurrentViewCtrl().collapseAll(); +} + + +function BTEditorTabBook::onTabClose(%this, %index) +{ + if(%index == %this.selectedPage) + { + BehaviorTreeInspector.inspect(-1); + if(%this.getCount() > 1) + %this.getObject(0).select(); + else + BTEditor.ResetUndoMenu(); + } + + %this.getObject(%index).delete(); +} + +function BTEditorTabBook::onTabSelected(%this, %text, %index) +{ + //echo("onTabSelected" TAB %text); + BTEditor.updateUndoMenu(); + BehaviorTreeInspector.inspect(BTEditor.getCurrentViewCtrl().getSelectedObject()); +} + +function BTEditorTabBook::onTabRightClick(%this, %text, %index) +{ + if(isObject(BTEditorTabBookPopup)) + BTEditorTabBookPopup.delete(); + + %popup = new PopupMenu( BTEditorTabBookPopup ) + { + superClass = "MenuBuilder"; + isPopup = true; + item[ 0 ] = "Close" SPC %text SPC "tab" TAB "" TAB "BTEditorTabBook.onTabClose(" SPC %index SPC ");"; + }; + + %popup.showPopup( Canvas ); +} + +//============================================================================== +// UNDO +//============================================================================== +function BTEditor::getUndoManager( %this ) +{ + return %this.getCurrentViewCtrl().getUndoManager(); +} + + +function BTEditor::updateUndoMenu(%this) +{ + %uman = %this.getUndoManager(); + %nextUndo = %uman.getNextUndoName(); + %nextRedo = %uman.getNextRedoName(); + + %editMenu = BTEditCanvas.menuBar.findMenu("Edit"); + + %editMenu.setItemName( 0, "Undo " @ %nextUndo ); + %editMenu.setItemName( 1, "Redo " @ %nextRedo ); + + %editMenu.enableItem( 0, %nextUndo !$= "" ); + %editMenu.enableItem( 1, %nextRedo !$= "" ); +} + +function BTEditor::ResetUndoMenu(%this) +{ + %editMenu = BTEditCanvas.menuBar.findMenu("Edit"); + %editMenu.setItemName( 0, "Undo" ); + %editMenu.setItemName( 1, "Redo" ); + %editMenu.enableItem( 0, false ); + %editMenu.enableItem( 1, false ); +} + +function BTEditor::undo(%this) +{ + %action = %this.getUndoManager().getNextUndoName(); + + %this.getUndoManager().undo(); + %this.updateUndoMenu(); + + BTEditorStatusBar.print( "Undid '" @ %action @ "'" ); +} + +function BTEditor::redo(%this) +{ + %action = %this.getUndoManager().getNextRedoName(); + + %this.getUndoManager().redo(); + %this.updateUndoMenu(); + + BTEditorStatusBar.print( "Redid '" @ %action @ "'" ); +} \ No newline at end of file diff --git a/Templates/BaseGame/game/tools/behaviorTreeEditor/scripts/behaviorTreeEditorCanvas.ed.cs b/Templates/BaseGame/game/tools/behaviorTreeEditor/scripts/behaviorTreeEditorCanvas.ed.cs new file mode 100644 index 0000000000..8cf37a4536 --- /dev/null +++ b/Templates/BaseGame/game/tools/behaviorTreeEditor/scripts/behaviorTreeEditorCanvas.ed.cs @@ -0,0 +1,159 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2014 Guy Allard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//----------------------------------------------------------------------------- + +//============================================================================== +// INIT +//============================================================================== +function BTEditCanvas::onAdd( %this ) +{ + %this.onCreateMenu(); + + // close any invalid tab book pages + for( %i=0; %i < BTEditorTabBook.getCount(); %i++) + { + %page = BTEditorTabBook.getObject(%i); + if(!isObject(%page.rootNode) || %page.rootNode.getClassName() !$= "Root") + { + BTEditorTabBook.remove(%page); + %page.delete(); + %i--; + } + } +} + +function BTEditCanvas::onRemove( %this ) +{ + if( isObject( BehaviorTreeEditorGui.menuGroup ) ) + BehaviorTreeEditorGui.delete(); + + //BTEditorTabBook.deleteAllObjects(); +} + +function BTEditCanvas::quit( %this ) +{ + // we must not delete a window while in its event handler, or we foul the event dispatch mechanism + %this.schedule(10, delete); + + Canvas.setContent(BTEditor.lastContent); + $InBehaviorTreeEditor = false; + BehaviorTreeManager.onBehaviorTreeEditor(false); + + // cleanup + %this.onDestroyMenu(); + + //Re-establish the main editor's menubar + EditorGui.attachMenus(); +} + +//============================================================================== +// MENU +//============================================================================== +function BTEditCanvas::onCreateMenu(%this) +{ + if(isObject(%this.menuBar)) + return; + + //set up %cmdctrl variable so that it matches OS standards + if( $platform $= "macos" ) + { + %cmdCtrl = "cmd"; + %redoShortcut = "Cmd-Shift Z"; + } + else + { + %cmdCtrl = "Ctrl"; + %redoShortcut = "Ctrl Y"; + } + + // Menu bar + %this.menuBar = new GuiMenuBar(BadBehaviorMenubar) + { + dynamicItemInsertPos = 3; + extent = "1024 20"; + minExtent = "320 20"; + horizSizing = "width"; + profile = "GuiMenuBarProfile"; + + new PopupMenu() + { + superClass = "MenuBuilder"; + barTitle = "File"; + internalName = "FileMenu"; + + item[0] = "New Tree..." TAB %cmdCtrl SPC "N" TAB "BTEditor.createTree();"; + item[1] = "Open..." TAB %cmdCtrl SPC "O" TAB %this @ ".open();"; + item[2] = "Save Tree" TAB %cmdCtrl SPC "S" TAB "BTEditor.saveTree( BTEditor.getCurrentRootNode(), false );"; + item[3] = "Save Tree As..." TAB %cmdCtrl @ "-Shift S" TAB "BTEditor.saveTree( BTEditor.getCurrentRootNode(), true );"; + item[4] = "-"; + item[5] = "Close Editor" TAB "F9" TAB %this @ ".quit();"; + item[6] = "Quit" TAB %cmdCtrl SPC "Q" TAB "quit();"; + }; + + new PopupMenu() + { + superClass = "MenuBuilder"; + barTitle = "Edit"; + internalName = "EditMenu"; + + item[0] = "Undo" TAB %cmdCtrl SPC "Z" TAB "BTEditor.undo();"; + item[1] = "Redo" TAB %redoShortcut TAB "BTEditor.redo();"; + item[2] = "-"; + item[3] = "Delete node" TAB "" TAB "BTEditor.getCurrentViewCtrl().deleteSelection();"; + item[4] = "Excise node" TAB "" TAB "BTEditor.getCurrentViewCtrl().exciseSelection();"; + }; + + new PopupMenu() + { + superClass = "MenuBuilder"; + barTitle = "View"; + internalName = "ViewMenu"; + + item[0] = "Expand All" TAB %cmdCtrl SPC "=" TAB "BTEditor.expandAll();"; + item[1] = "Collapse All" TAB %cmdCtrl SPC "-" TAB "BTEditor.collapseAll();"; + }; + + new PopupMenu() + { + superClass = "MenuBuilder"; + internalName = "HelpMenu"; + + barTitle = "Help"; + + Item[0] = "Help will arrive soon......"; + }; + }; + %this.menuBar.attachToCanvas(Canvas, 0); +} + +function BTEditCanvas::onDestroyMenu(%this) +{ + if( !isObject( %this.menuBar ) ) + return; + + // Destroy menus + while( %this.menuBar.getCount() != 0 ) + %this.menuBar.getObject( 0 ).delete(); + + %this.menuBar.removeFromCanvas(); + %this.menuBar.delete(); +} + diff --git a/Templates/BaseGame/game/tools/behaviorTreeEditor/scripts/behaviorTreeEditorContentList.ed.cs b/Templates/BaseGame/game/tools/behaviorTreeEditor/scripts/behaviorTreeEditorContentList.ed.cs new file mode 100644 index 0000000000..d59e37704b --- /dev/null +++ b/Templates/BaseGame/game/tools/behaviorTreeEditor/scripts/behaviorTreeEditorContentList.ed.cs @@ -0,0 +1,133 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2014 Guy Allard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//----------------------------------------------------------------------------- + +function BTEditorContentList::refresh(%this) +{ + %this.clear(); + foreach(%tree in BehaviorTreeGroup) + %this.add(%tree.name, %tree); +} + +function BTEditorContentList::onSelect( %this, %tree ) +{ + BTEditor.viewTree( %tree ); +} + + + +function BTEditor::newPage() +{ + %page = new GuiTabPageCtrl() { + fitBook = "1"; + maxLength = "1024"; + docking = "client"; + margin = "-1 0 0 0"; + padding = "0 0 0 0"; + anchorTop = "1"; + anchorBottom = "0"; + anchorLeft = "1"; + anchorRight = "0"; + position = "0 0 "; + extent = "200 200"; + minExtent = "8 2"; + horizSizing = "width"; + vertSizing = "height"; + profile = "GuiTabPageProfile"; + visible = "1"; + active = "1"; + tooltipProfile = "GuiToolTipProfile"; + hovertime = "1000"; + isContainer = "1"; + canSave = "0"; + canSaveDynamicFields = "0"; + + new GuiScrollCtrl() { + willFirstRespond = "1"; + hScrollBar = "dynamic"; + vScrollBar = "alwaysOn"; + lockHorizScroll = "0"; + lockVertScroll = "0"; + constantThumbHeight = "0"; + childMargin = "1 1"; + mouseWheelScrollSpeed = "-1"; + margin = "0 0 0 0"; + padding = "0 0 0 0"; + anchorTop = "1"; + anchorBottom = "0"; + anchorLeft = "1"; + anchorRight = "0"; + position = "0 0"; + extent = "400 400"; + minExtent = "8 2"; + horizSizing = "width"; + vertSizing = "height"; + profile = "GuiScrollProfile"; + visible = "1"; + active = "1"; + tooltipProfile = "GuiToolTipProfile"; + hovertime = "1000"; + isContainer = "1"; + canSave = "0"; + docking="client"; + canSaveDynamicFields = "0"; + + new GuiBehaviorTreeViewCtrl() { + internalName="BTView"; + tabSize = "17"; + textOffset = "8"; + fullRowSelect = "0"; + itemHeight = "21"; + destroyTreeOnSleep = "0"; + mouseDragging = "1"; + multipleSelections = "0"; + deleteObjectAllowed = "1"; + dragToItemAllowed = "1"; + clearAllOnSingleSelection = "1"; + showRoot = "1"; + useInspectorTooltips = "0"; + tooltipOnWidthOnly = "0"; + showObjectIds = "0"; + showClassNames = "1"; + showObjectNames = "1"; + showInternalNames = "1"; + showClassNameForUnnamedObjects = "0"; + compareToObjectID = "1"; + canRenameObjects = "1"; + renameInternal = "0"; + position = "1 1"; + extent = "267 231"; + minExtent = "8 2"; + horizSizing = "right"; + vertSizing = "bottom"; + profile = "GuiBehaviorTreeViewProfile"; + visible = "1"; + active = "1"; + tooltipProfile = "GuiToolTipProfile"; + hovertime = "1000"; + isContainer = "1"; + canSave = "0"; + canSaveDynamicFields = ""; + }; + }; + }; + return %page; +} \ No newline at end of file diff --git a/Templates/BaseGame/game/tools/behaviorTreeEditor/scripts/behaviorTreeEditorInspector.ed.cs b/Templates/BaseGame/game/tools/behaviorTreeEditor/scripts/behaviorTreeEditorInspector.ed.cs new file mode 100644 index 0000000000..7ae07a224b --- /dev/null +++ b/Templates/BaseGame/game/tools/behaviorTreeEditor/scripts/behaviorTreeEditorInspector.ed.cs @@ -0,0 +1,50 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2014 Guy Allard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//----------------------------------------------------------------------------- + +//============================================================================== +// inspector field changed +//============================================================================== +function BehaviorTreeInspector::onInspectorFieldModified( %this, %object, %fieldName, %arrayIndex, %oldValue, %newValue ) +{ + //echo("onInspectorFieldModified" SPC %object SPC %fieldName SPC %arrayIndex SPC %oldValue SPC %newValue); + + // select the correct page + %treeRoot = BTEditor.getTreeRoot(%object); + %targetView = -1; + foreach(%page in BTEditorTabBook) + { + %view = %page-->BTView; + if(%view.getRootNode() == %treeRoot) + %targetView = %view; + } + + if(!isObject(%targetView)) + { + warn("OOPS - something went wrong with the inspector!"); + return; + } + + %action = BTInspectorUndoAction::create(%object, %fieldName, %arrayIndex, %oldValue, %newValue); + %action.addToManager( %targetView.getUndoManager() ); + + BTEditor.updateUndoMenu(); +} diff --git a/Templates/BaseGame/game/tools/behaviorTreeEditor/scripts/behaviorTreeEditorProfiles.ed.cs b/Templates/BaseGame/game/tools/behaviorTreeEditor/scripts/behaviorTreeEditorProfiles.ed.cs new file mode 100644 index 0000000000..80bee0fd12 --- /dev/null +++ b/Templates/BaseGame/game/tools/behaviorTreeEditor/scripts/behaviorTreeEditorProfiles.ed.cs @@ -0,0 +1,26 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2014 Guy Allard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//----------------------------------------------------------------------------- + +singleton GuiControlProfile(GuiBehaviorTreeViewProfile : ToolsGuiTreeViewProfile) +{ + bitmap = "tools/behaviorTreeEditor/images/BehaviorTreeView.png"; +}; \ No newline at end of file diff --git a/Templates/BaseGame/game/tools/behaviorTreeEditor/scripts/behaviorTreeEditorStatusBar.ed.cs b/Templates/BaseGame/game/tools/behaviorTreeEditor/scripts/behaviorTreeEditorStatusBar.ed.cs new file mode 100644 index 0000000000..9ee9e78969 --- /dev/null +++ b/Templates/BaseGame/game/tools/behaviorTreeEditor/scripts/behaviorTreeEditorStatusBar.ed.cs @@ -0,0 +1,41 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2014 Guy Allard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//----------------------------------------------------------------------------- + +function BTEditorStatusBar::print( %this, %message ) +{ + %this.setText( %message ); + + %sequenceNum = %this.sequenceNum + 1; + %this.sequenceNum = %sequenceNum; + + %this.schedule( 4 * 1000, "clearMessage", %sequenceNum ); +} + +//--------------------------------------------------------------------------------------------- + +function BTEditorStatusBar::clearMessage( %this, %sequenceNum ) +{ + // If we had no newer message in the meantime, clear + // out the current text. + + %this.setText( "" ); +} \ No newline at end of file diff --git a/Templates/BaseGame/game/tools/behaviorTreeEditor/scripts/behaviorTreeEditorUndo.ed.cs b/Templates/BaseGame/game/tools/behaviorTreeEditor/scripts/behaviorTreeEditorUndo.ed.cs new file mode 100644 index 0000000000..9cbbbb1a94 --- /dev/null +++ b/Templates/BaseGame/game/tools/behaviorTreeEditor/scripts/behaviorTreeEditorUndo.ed.cs @@ -0,0 +1,174 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2014 Guy Allard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//----------------------------------------------------------------------------- + +function BTEditorUndoManager::onAddUndo( %this ) +{ + echo("Undo Added"); + BTEditor.updateUndoMenu(); +} + +//============================================================================== +// create a node +//============================================================================== +function BTCreateUndoAction::submit( %undoObject ) +{ + // The instant group will try to add our + // UndoAction if we don't disable it. + pushInstantGroup(); + + // Create the undo action. + %action = new MECreateUndoAction() + { + className = "BTCreateUndoAction"; + actionName = "Create " @ %undoObject.getClassName(); + }; + + // Restore the instant group. + popInstantGroup(); + + // Set the object to undo. + %action.addObject( %undoObject ); + + // Submit it. + %action.addToManager( BTEditor.getUndoManager() ); +} + +function BTCreateUndoAction::onUndone( %this ) +{ + //EWorldEditor.syncGui(); +} + +function BTCreateUndoAction::onRedone( %this ) +{ + //EWorldEditor.syncGui(); +} + + + +//============================================================================== +// delete a node +//============================================================================== + +/// A helper for submitting a delete undo action. +function BTDeleteUndoAction::submit( %deleteObject ) +{ + // The instant group will try to add our + // UndoAction if we don't disable it. + pushInstantGroup(); + + // Create the undo action. + %action = new BTDeleteUndoAction() + { + actionName = "delete node"; + }; + + // Restore the instant group. + popInstantGroup(); + + %action.deleteObject( %deleteObject ); + + // Submit it. + %action.addToManager( BTEditor.getUndoManager() ); +} + +function BTDeleteUndoAction::onUndone( %this ) +{ + // for some reason this doesn't restore the correct order + //BTEditor.getCurrentViewCtrl().buildVisibleTree(true); + // so re-open the tree instead + BTEditor.getCurrentViewCtrl().refresh(); +} + +function BTDeleteUndoAction::onRedone( %this ) +{ + BTEditor.getCurrentViewCtrl().buildVisibleTree(true); +} + +//============================================================================== +// reparent a node +//============================================================================== +function BTReparentUndoAction::create( %treeView ) +{ + pushInstantGroup(); + %action = new UndoScriptAction() + { + class = "BTReparentUndoAction"; + actionName = "move node"; + control = %treeView; + }; + popInstantGroup(); + + return %action; +} + +function BTReparentUndoAction::undo(%this) +{ + if(%this.newParent.isMember(%this.node)) + %this.newParent.remove(%this.node); + + %this.oldParent.add(%this.node); + + if(%this.oldPosition < %this.oldParent.getCount() - 1) + %this.oldParent.reorderChild(%this.node, %this.oldParent.getObject(%this.oldPosition)); + + %this.control.refresh(); +} + +function BTReparentUndoAction::redo(%this) +{ + if(%this.oldParent.isMember(%this.node)) + %this.oldParent.remove(%this.node); + + %this.newParent.add(%this.node); + + if(%this.newPosition < %this.newParent.getCount() - 1) + %this.newParent.reorderChild(%this.node, %this.newParent.getObject(%this.newPosition)); + + %this.control.refresh(); +} + +//============================================================================== +// Inspector field modified +//============================================================================== +function BTInspectorUndoAction::create(%object, %fieldName, %arrayIndex, %oldValue, %newValue ) +{ + %nameOrClass = %object.getName(); + if ( %nameOrClass $= "" ) + %nameOrClass = %object.getClassname(); + + pushInstantGroup(); + %action = new InspectorFieldUndoAction() + { + class = "BTInspectorUndoAction"; + actionName = %nameOrClass @ "." @ %fieldName @ " Change"; + + objectId = %object.getId(); + fieldName = %fieldName; + fieldValue = %oldValue; + arrayIndex = %arrayIndex; + + inspectorGui = BehaviorTreeInspector; + }; + popInstantGroup(); + + return %action; +} diff --git a/Templates/BaseGame/game/tools/behaviorTreeEditor/scripts/guiBehaviorTreeViewCtrl.ed.cs b/Templates/BaseGame/game/tools/behaviorTreeEditor/scripts/guiBehaviorTreeViewCtrl.ed.cs new file mode 100644 index 0000000000..63e5f23b53 --- /dev/null +++ b/Templates/BaseGame/game/tools/behaviorTreeEditor/scripts/guiBehaviorTreeViewCtrl.ed.cs @@ -0,0 +1,433 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2014 Guy Allard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//----------------------------------------------------------------------------- + +function GuiBehaviorTreeViewCtrl::getRootNode(%this) +{ + %rootId = %this.getFirstRootItem(); + %rootObj = %rootId ? %this.getItemValue(%rootId) : -1; + return %rootObj; +} + +function GuiBehaviorTreeViewCtrl::getUndoManager( %this ) +{ + if( !isObject( %this.undoManager ) ) + { + %this.undoManager = new UndoManager(){ numLevels=20; }; + %this.undoManager.setClassNamespace("BTEditorUndoManager"); + } + + return %this.undoManager; +} + +function GuiBehaviorTreeViewCtrl::onRemove(%this) +{ + if(isObject(%this.undoManager)) + %this.undoManager.delete(); +} + +function GuiBehaviorTreeViewCtrl::expandAll(%this) +{ + for(%i=1; %i<=%this.getItemCount(); %i++) + { + %this.expandItem(%i); + %this.buildVisibleTree(); + } +} + +function GuiBehaviorTreeViewCtrl::collapseAll(%this) +{ + for(%i=1; %i<=%this.getItemCount(); %i++) + %this.expandItem(%i, false); + %this.buildVisibleTree(); +} + +function GuiBehaviorTreeViewCtrl::refresh(%this) +{ + %root = %this.getRootNode(); + %this.open(%root); + %this.expandAll(); + %this.selectItem(1); +} + +//============================================================================== +// DELETE +//============================================================================== +// onDeleteSelection is called prior to deleting the selected object. +function GuiBehaviorTreeViewCtrl::onDeleteSelection(%this) +{ + if(%this.getSelectedItem() > 1) // not root + BTDeleteUndoAction::submit(%this.getSelectedObject()); + + %this.clearSelection(); + + BTEditorStatusBar.print( "Node deleted" ); +} + +//============================================================================== +// SELECT +//============================================================================== +function GuiBehaviorTreeViewCtrl::onSelect(%this, %item) +{ + BehaviorTreeInspector.inspect(%item); +} + +function GuiBehaviorTreeViewCtrl::onBeginReparenting(%this) +{ + if( isObject( %this.reparentUndoAction ) ) + %this.reparentUndoAction.delete(); + + %action = BTReparentUndoAction::create( %this ); + %action.node = %this.getSelectedObject(); + %parent = %action.node.getGroup(); + %action.oldPosition = 0; + + if(isObject(%parent)) + %action.oldPosition = %parent.getObjectIndex(%action.node); + + %this.reparentUndoAction = %action; +} + +function GuiBehaviorTreeViewCtrl::onReparent(%this, %item, %old, %new) +{ + if( !isObject(%this.reparentUndoAction) || + %this.reparentUndoAction.node != %item ) + { + warn( "Reparenting undo is borked :(" ); + if(isObject(%this.reparentUndoAction)) + { + %this.reparentUndoAction.delete(); + %this.reparentUndoAction=""; + } + } + else + { + %this.reparentUndoAction.oldParent = %old; + %this.reparentUndoAction.newParent = %new; + %this.reparentUndoAction.newPosition = %new.getObjectIndex(%item); + } +} + +function GuiBehaviorTreeViewCtrl::onEndReparenting( %this ) +{ + %action = %this.reparentUndoAction; + %this.reparentUndoAction = ""; + + // Check that the reparenting went as planned, and undo it right now if not + if(%action.node.getGroup() != %action.newParent) + { + %action.undo(); + %action.delete(); + } + else + { + %action.addToManager( %this.getUndoManager() ); + BTEditorStatusBar.print( "Moved node" ); + } +} + +function GuiBehaviorTreeViewCtrl::canAdd(%this, %obj, %target) +{ + if(!isObject(%target)) + return false; + + if( !%target.isMemberOfClass( "SimGroup" ) ) + return false; + + return %target.acceptsAsChild(%obj); +} + +function GuiBehaviorTreeViewCtrl::isValidDragTarget(%this, %id, %obj) +{ + %selObj = %this.getSelectedObject(); + + if(!%selObj) + return false; + + return %this.canAdd(%selObj, %obj); +} + +function GuiBehaviorTreeViewCtrl::onMouseUp(%this, %itemid, %count) +{ + %this.mouseDragging=true; +} + +function GuiBehaviorTreeViewCtrl::onRightMouseDown(%this, %item, %pos, %obj) +{ + //echo("onRightMouseDown" TAB %item TAB %pos TAB %obj); + + %selObj = %this.getSelectedObject(); + if(!isObject(%selObj)) + return; + + %this.mouseDragging=false; + + if(isObject(BTEditPopup)) + BTEditPopup.delete(); + + // construct the popup + %popup = new PopupMenu(BTEditPopup) { + superClass = "MenuBuilder"; + isPopup = true; + }; + + // current node + %popup.addItem(0, %selObj.getClassName() SPC "[" @ %selObj.internalName @ "]" TAB "" TAB ""); + %popup.enableItem(0, false); + + // divider + %popup.addItem(1, "-"); + + // add node submenu + %popup.addItem(2, "Add node" TAB %this.makePopup(false, %selObj) TAB ""); + %popup.enableItem(2, %this.validate(false, %selObj)); + + // insert node submenu + %popup.addItem(3, "Insert node" TAB %this.makePopup(true, %selObj) TAB ""); + %popup.enableItem(3, %this.validate(true, %selObj)); + + // divider + %popup.addItem(4, "-"); + + // delete node + %popup.addItem(5, "Delete node" TAB "" TAB %this @ ".deleteSelection();"); + + // excise node (delete single node, reparenting children) + %popup.addItem(6, "Excise node" TAB "" TAB %this @ ".exciseSelection();"); + %popup.enableItem(6, (%this.getSelectedObject().getCount() == 1) && + (%this.getSelectedObject() != %this.getRootNode()) ); + + BTEditPopup.showPopup( Canvas ); +} + +function GuiBehaviorTreeViewCtrl::onRightMouseUp(%this, %item, %pos, %obj) +{ + //echo("onRightMouseUp" TAB %item TAB %pos TAB %obj); +} + +function GuiBehaviorTreeViewCtrl::makePopup(%this, %insert, %selObj) +{ + %popup = new PopupMenu() { + superClass = "MenuBuilder"; + isPopup = true; + }; + + for(%i=0; %i 0 : false; + %newTypeBaseType = BTEditor.getBaseNodeType(%newType); + + if(!%insert) + { + return !( (%selObjClass $= "Root" && %selObjHasChild) || + (%selObjBaseType $= "Leaf") || + (%selObjBaseType $= "Decorator" && %selObjHasChild) ); + } + else + { + return !( (%selObjClass $= "Root") || + (%newTypeBaseType $= "Leaf") ); + } +} + + +function GuiBehaviorTreeViewCtrl::addNode(%this, %selObj, %type) +{ + pushInstantGroup(%selObj); + %newNode = new(%type)(); + popInstantGroup(); + + %this.expandItem(%this.findItemByObjectId(%selObj.getId())); + %this.buildVisibleTree(); + + if(isObject(%newNode)) + BTCreateUndoAction::submit( %newNode ); + + // have to delay selection a bit + %this.schedule(10, selectItem, %this.findItemByObjectId(%newNode.getId())); +} + +function GuiBehaviorTreeViewCtrl::insertNode(%this, %selObj, %type) +{ + // steps are + // 1) Remove selected object from tree + // 2) Create the new object as a child of the selected object's parent + // 3) Move the new object into the correct position + // 4) Make the new object the parent of the old object + + // need a compound undo for this process + %this.getUndoManager().pushCompound( "Insert new" SPC %type ); + + // step 1 + // remove selected object from tree (add it to BehaviorTreeGroup) + %parent = %selObj.getGroup(); + %pos = %parent.getObjectIndex(%selObj); + BehaviorTreeGroup.add(%selObj); + + // Undo for step 1 + %action = BTReparentUndoAction::create(%this); + %action.node = %selObj; + %action.oldPosition = %pos; + %action.oldParent = %parent; + %action.newParent = BehaviorTreeGroup; + %action.newPosition = 0; + %action.addToManager(%this.getUndoManager()); + + // Step 2 + // Create new object as child of selected object's parent + pushInstantGroup(%parent); + %newNode = new(%type)(); + popInstantGroup(); + + // undo for step 2 + if(isObject(%newNode)) + BTCreateUndoAction::submit( %newNode ); + + // step 3 + // move new object to correct position + if(%pos < %parent.getCount() - 1) + { + %parent.reorderChild(%newNode, %parent.getObject(%pos)); + + // undo for step 3 + %action = BTReparentUndoAction::create(%this); + %action.node = %newNode; + %action.oldPosition = 0; + %action.oldParent = %parent; + %action.newParent = %parent; + %action.newPosition = %pos; + %action.addToManager(%this.getUndoManager()); + } + + // step 4 + // Make the new object the parent of the old object + %newNode.add(%selObj); + + // undo for step 4 + %action = BTReparentUndoAction::create(%this); + %action.node = %selObj; + %action.oldPosition = 0; + %action.oldParent = BehaviorTreeGroup; + %action.newParent = %newNode; + %action.newPosition = 0; + %action.addToManager(%this.getUndoManager()); + + // finalize the compound undo + %this.getUndoManager().popCompound(); + + // re-draw + %this.refresh(); +} + +// remove a single node from within the hierarchy, reparenting its children +function GuiBehaviorTreeViewCtrl::exciseSelection(%this) +{ + // steps are: + // 1) remove the selected node from the tree + // 2) re-parent the selected node's child to the old parent + // 3) delete the selected node + %selObj = %this.getSelectedObject(); + if(%selObj.getCount() != 1 || %selObj == %this.getRootNode()) + { + warn("Excision got messy"); + return; + } + + %child = %selObj.getObject(0); + %parent = %selObj.getGroup(); + %pos = %parent.getObjectIndex(%selObj); + + // need a compound undo for this process + %this.getUndoManager().pushCompound( "Excise" SPC %type ); + + // step 1 + // remove selected object from tree (add it to BehaviorTreeGroup) + BehaviorTreeGroup.add(%selObj); + + // Undo for step 1 + %action = BTReparentUndoAction::create(%this); + %action.node = %selObj; + %action.oldPosition = %pos; + %action.oldParent = %parent; + %action.newParent = BehaviorTreeGroup; + %action.newPosition = 0; + %action.addToManager(%this.getUndoManager()); + + // step 2 + // re-parent the selected node's child to the old parent + %parent.add(%child); + if(%pos < %parent.getCount() - 1) + { + %parent.reorderChild(%child, %parent.getObject(%pos)); + } + + // undo for step 2 + %action = BTReparentUndoAction::create(%this); + %action.node = %child; + %action.oldPosition = 0; + %action.oldParent = %selObj; + %action.newParent = %parent; + %action.newPosition = %pos; + %action.addToManager(%this.getUndoManager()); + + // step 3 + // delete the selected node + %action = new BTDeleteUndoAction(); + %action.deleteObject( %selObj ); + %action.addToManager(%this.getUndoManager()); + + // finalize the compound undo + %this.getUndoManager().popCompound(); + + // re-draw + %this.refresh(); + + %this.selectItem(%this.findItemByObjectId(%child.getId())); +} \ No newline at end of file diff --git a/Templates/Modules/badBehaviour/badBehaviour.cs b/Templates/Modules/badBehaviour/badBehaviour.cs new file mode 100644 index 0000000000..50b85de86c --- /dev/null +++ b/Templates/Modules/badBehaviour/badBehaviour.cs @@ -0,0 +1,20 @@ +//----------------------------------------------------------------------------- +// Module creation functions. +//----------------------------------------------------------------------------- + +function badBehaviour::create( %this ) +{ +} + +function badBehaviour::destroy( %this ) +{ + +} + +function badBehaviour::initClient( %this ) +{ + %this.queueExec("scripts/server/behaviorTreeManager.cs"); + %this.queueExec("scripts/server/badBot.cs"); + %this.queueExec("scripts/server/botMatch.cs"); +} + diff --git a/Templates/Modules/badBehaviour/badBehaviour.module b/Templates/Modules/badBehaviour/badBehaviour.module new file mode 100644 index 0000000000..cd6fc196f4 --- /dev/null +++ b/Templates/Modules/badBehaviour/badBehaviour.module @@ -0,0 +1,9 @@ + + \ No newline at end of file diff --git a/Templates/Modules/badBehaviour/levels/BehaviorTestbed.mis b/Templates/Modules/badBehaviour/levels/BehaviorTestbed.mis new file mode 100644 index 0000000000..253b9708cc --- /dev/null +++ b/Templates/Modules/badBehaviour/levels/BehaviorTestbed.mis @@ -0,0 +1,226 @@ +//--- OBJECT WRITE BEGIN --- +new SimGroup(MissionGroup) { + canSave = "1"; + canSaveDynamicFields = "1"; + enabled = "1"; + + new LevelInfo(theLevelInfo) { + nearClip = "0.1"; + visibleDistance = "1000"; + decalBias = "0.0015"; + fogColor = "0.6 0.6 0.7 1"; + fogDensity = "0"; + fogDensityOffset = "700"; + fogAtmosphereHeight = "0"; + canvasClearColor = "0 0 0 255"; + ambientLightBlendPhase = "1"; + ambientLightBlendCurve = "0 0 -1 -1"; + advancedLightmapSupport = "0"; + soundAmbience = "AudioAmbienceDefault"; + soundDistanceModel = "Linear"; + canSave = "1"; + canSaveDynamicFields = "1"; + desc0 = "Somewhere to play with behavior trees"; + enabled = "1"; + levelName = "Behavior Testbed"; + }; + new SkyBox(theSky) { + Material = "BlackSkyMat"; + drawBottom = "0"; + fogBandHeight = "0"; + position = "0 0 0"; + rotation = "1 0 0 0"; + scale = "1 1 1"; + canSave = "1"; + canSaveDynamicFields = "1"; + }; + new Sun(theSun) { + azimuth = "230.396"; + elevation = "45"; + color = "0.996078 0.996078 0.996078 1"; + ambient = "0.560784 0.564706 0.572549 1"; + brightness = "1"; + castShadows = "1"; + coronaEnabled = "1"; + coronaScale = "0.5"; + coronaTint = "1 1 1 1"; + coronaUseLightColor = "1"; + flareScale = "1"; + attenuationRatio = "0 1 1"; + shadowType = "PSSM"; + texSize = "1024"; + overDarkFactor = "3000 2000 1000 250"; + shadowDistance = "400"; + shadowSoftness = "0.25"; + numSplits = "4"; + logWeight = "0.96"; + fadeStartDistance = "325"; + lastSplitTerrainOnly = "0"; + representedInLightmap = "0"; + shadowDarkenColor = "0 0 0 -1"; + includeLightmappedGeometryInShadow = "0"; + position = "0 0 0"; + rotation = "1 0 0 0"; + scale = "1 1 1"; + canSave = "1"; + canSaveDynamicFields = "1"; + }; + new SimGroup(PlayerDropPoints) { + canSave = "1"; + canSaveDynamicFields = "1"; + enabled = "1"; + + new SpawnSphere() { + autoSpawn = "0"; + spawnTransform = "0"; + radius = "5"; + sphereWeight = "1"; + indoorWeight = "1"; + outdoorWeight = "1"; + isAIControlled = "0"; + dataBlock = "SpawnSphereMarker"; + position = "0 0 2"; + rotation = "1 0 0 0"; + scale = "1 1 1"; + canSave = "1"; + canSaveDynamicFields = "1"; + enabled = "1"; + homingCount = "0"; + lockCount = "0"; + }; + }; + new GroundPlane() { + squareSize = "100"; + scaleU = "5"; + scaleV = "5"; + Material = "Grid512_ForestGreenLines_Mat"; + canSave = "1"; + canSaveDynamicFields = "1"; + enabled = "1"; + position = "0 0 0"; + rotation = "1 0 0 0"; + scale = "1 1 1"; + }; + new SimGroup(podia) { + canSave = "1"; + canSaveDynamicFields = "1"; + + new ConvexShape() { + Material = "Grid512_OrangeLines_Mat"; + position = "10 -10 0.500001"; + rotation = "1 0 0 0"; + scale = "1 1 1"; + canSave = "1"; + canSaveDynamicFields = "1"; + + surface = "0 0 0 1 4.05839e-010 1.44067e-009 0.112745"; + surface = "0 1 0 0 5.05789e-009 -4.56528e-009 -0.5"; + surface = "0.311251 0 0 0.950328 1.59573e-009 1.39021 -0.163826"; + surface = "0 -0.311251 0.950328 -0 4.85838e-010 -1.39021 -0.163826"; + surface = "0.220088 0.220088 -0.671983 0.671983 -1.39021 -3.2702e-008 -0.163826"; + surface = "0.220088 -0.220088 0.671983 0.671983 1.39021 -2.28065e-008 -0.163826"; + }; + new ConvexShape() { + Material = "Grid512_OrangeLines_Mat"; + position = "-10 -10 0.500001"; + rotation = "1 0 0 0"; + scale = "1 1 1"; + canSave = "1"; + canSaveDynamicFields = "1"; + + surface = "0 0 0 1 4.05839e-010 1.44067e-009 0.112745"; + surface = "0 1 0 0 5.05789e-009 -4.56528e-009 -0.5"; + surface = "0.311251 0 0 0.950328 1.59573e-009 1.39021 -0.163826"; + surface = "0 -0.311251 0.950328 -0 4.85838e-010 -1.39021 -0.163826"; + surface = "0.220088 0.220088 -0.671983 0.671983 -1.39021 -3.2702e-008 -0.163826"; + surface = "0.220088 -0.220088 0.671983 0.671983 1.39021 -2.28065e-008 -0.163826"; + }; + new ConvexShape() { + Material = "Grid512_OrangeLines_Mat"; + position = "-10 10 0.500001"; + rotation = "1 0 0 0"; + scale = "1 1 1"; + canSave = "1"; + canSaveDynamicFields = "1"; + + surface = "0 0 0 1 4.05839e-010 1.44067e-009 0.112745"; + surface = "0 1 0 0 5.05789e-009 -4.56528e-009 -0.5"; + surface = "0.311251 0 0 0.950328 1.59573e-009 1.39021 -0.163826"; + surface = "0 -0.311251 0.950328 -0 4.85838e-010 -1.39021 -0.163826"; + surface = "0.220088 0.220088 -0.671983 0.671983 -1.39021 -3.2702e-008 -0.163826"; + surface = "0.220088 -0.220088 0.671983 0.671983 1.39021 -2.28065e-008 -0.163826"; + }; + new ConvexShape() { + Material = "Grid512_OrangeLines_Mat"; + position = "10 10 0.500001"; + rotation = "1 0 0 0"; + scale = "1 1 1"; + canSave = "1"; + canSaveDynamicFields = "1"; + + surface = "0 0 0 1 4.05839e-010 1.44067e-009 0.112745"; + surface = "0 1 0 0 5.05789e-009 -4.56528e-009 -0.5"; + surface = "0.311251 0 0 0.950328 1.59573e-009 1.39021 -0.163826"; + surface = "0 -0.311251 0.950328 -0 4.85838e-010 -1.39021 -0.163826"; + surface = "0.220088 0.220088 -0.671983 0.671983 -1.39021 -3.2702e-008 -0.163826"; + surface = "0.220088 -0.220088 0.671983 0.671983 1.39021 -2.28065e-008 -0.163826"; + }; + }; + new SimGroup(Paths) { + canSave = "1"; + canSaveDynamicFields = "1"; + + new Path(PatrolPath) { + isLooping = "1"; + canSave = "1"; + canSaveDynamicFields = "1"; + + new Marker() { + seqNum = "0"; + type = "Normal"; + msToNext = "1000"; + smoothingType = "Spline"; + position = "-10 10 0.928092"; + rotation = "0 0 -1 48.0534"; + scale = "1 1 1"; + canSave = "1"; + canSaveDynamicFields = "1"; + }; + new Marker() { + seqNum = "1"; + type = "Normal"; + msToNext = "1000"; + smoothingType = "Spline"; + position = "10 10 0.928092"; + rotation = "0 0 1 46.6231"; + scale = "1 1 1"; + canSave = "1"; + canSaveDynamicFields = "1"; + }; + new Marker() { + seqNum = "2"; + type = "Normal"; + msToNext = "1000"; + smoothingType = "Spline"; + position = "10 -10 0.928092"; + rotation = "0 0 1 137.031"; + scale = "1 1 1"; + canSave = "1"; + canSaveDynamicFields = "1"; + }; + new Marker() { + seqNum = "3"; + type = "Normal"; + msToNext = "1000"; + smoothingType = "Spline"; + position = "-10 -10 0.928092"; + rotation = "0 0 1 225.424"; + scale = "1 1 1"; + canSave = "1"; + canSaveDynamicFields = "1"; + }; + }; + }; +}; +//--- OBJECT WRITE END --- + diff --git a/Templates/Modules/badBehaviour/levels/BehaviorTestbed_preview.png b/Templates/Modules/badBehaviour/levels/BehaviorTestbed_preview.png new file mode 100644 index 0000000000..a440c9e1d4 Binary files /dev/null and b/Templates/Modules/badBehaviour/levels/BehaviorTestbed_preview.png differ diff --git a/Templates/Modules/badBehaviour/scripts/server/badBot.cs b/Templates/Modules/badBehaviour/scripts/server/badBot.cs new file mode 100644 index 0000000000..0756505915 --- /dev/null +++ b/Templates/Modules/badBehaviour/scripts/server/badBot.cs @@ -0,0 +1,478 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2014 Guy Allard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//----------------------------------------------------------------------------- + +//----------------------------------------------------------------------------- +// Base frequency for behavior tree ticks (in milliseconds) +//----------------------------------------------------------------------------- +$BotTickFrequency = 100; + +// ---------------------------------------------------------------------------- +// make the local player invisible to the AI +//----------------------------------------------------------------------------- +function setGodMode(%val) +{ + LocalClientConnection.player.isGod = %val; +} + +// don't damage an invincible player +function DefaultPlayerData::damage(%this, %obj, %sourceObject, %position, %damage, %damageType) +{ + if(%obj.isGod) + return; + + Parent::damage(%this, %obj, %sourceObject, %position, %damage, %damageType); +} + +//----------------------------------------------------------------------------- +// bot datablock +//----------------------------------------------------------------------------- +datablock PlayerData(BadBotData : DefaultPlayerData) +{ + // max visible distance + VisionRange = 40; + + // vision field of view + VisionFov = 120; + + // max range to look for items + findItemRange = 20; + + // the type of object to search for when looking for targets + targetObjectTypes = $TypeMasks::PlayerObjectType; + + // the type of object to search for when looking for items + itemObjectTypes = $TypeMasks::itemObjectType; + + // some numbers for testing + + // distance the bot wants to be from its target when using the Ryder + optimalRange["Ryder"] = 8; + + // number of milliseconds to hold the trigger down when using the Ryder + burstLength["Ryder"] = 100; + + // distance the bot wants to be from its target when using the Lurker + optimalRange["Lurker"] = 12; + + // number of milliseconds to hold the trigger down when using the Lurker + burstLength["Lurker"] = 750; + + // +/- deviation from optimal range that is tolerated + rangeTolerance = 3; + + // probability that the bot will switch from its current target to another, closer target + switchTargetProbability = 0.1; + + // disable other weapons, we don't know how to use them yet + maxInv[LurkerGrenadeLauncher] = 0; + maxInv[LurkerGrenadeAmmo] = 0; + maxInv[ProxMine] = 0; + maxInv[DeployableTurret] = 0; +}; + + +//============================================================================= +// Supporting functions for an AIPlayer driven by a behavior tree +//============================================================================= + +// Spawn a bot called %name located at %startpos +function BadBot::spawn(%name, %startPos) +{ + // create the bot + %bot = new AIPlayer(%name) { + dataBlock = BadBotData; + class = "BadBot"; + }; + + // give it a name + if(%name !$= "") + %bot.setShapeName(%name); + + // set its position, or use the default if no position is given + if(isObject(%startPos)) + { + %startPos = %startPos.position; + } + else if(%startPos $= "") + { + %spawnPoint = pickPlayerSpawnPoint(PlayerDropPoints); + if(isObject(%spawnPoint)) + %startPos = %spawnPoint.getPosition(); + } + + %bot.setPosition(%startPos); + + // tetherpoint will give the bot a place to call home + %bot.tetherPoint = %startPos; + + return %bot; +} + + +// override getMuzzleVector so that the bots aim at where they are looking +function BadBot::getMuzzleVector(%this, %slot) +{ + return %this.getEyeVector(); +} + + +// use onAdd to equip the bot +function BadBotData::onAdd(%data, %obj) +{ + // give him the standard player loadout + game.loadout(%obj); +} + + +// Override onDisabled so we can stop running the behavior tree +function BadBotData::onDisabled(%this, %obj, %state) +{ + %obj.behaviorTree.stop(); + Parent::onDisabled(%this, %obj, %state); +} + + +// moveTo command, %dest can be either a location or an object +function BadBot::moveTo(%this, %dest, %slowDown) +{ + %pos = isObject(%dest) ? %dest.getPosition() : %dest; + %this.setMoveDestination(%pos, %slowDown); + %obj.atDestination = false; +} + +// forward onReachDestination to the behavior tree as a signal +function BadBotData::onReachDestination(%data, %obj) +{ + if(isObject(%obj.behaviorTree)) + %obj.behaviorTree.postSignal("onReachDestination"); + + %obj.atDestination = true; +} + +// forward animationDone callback to the behavior tree as a signal +function BadBotData::animationDone(%data, %obj) +{ + if(isObject(%obj.behaviorTree)) + %obj.behaviorTree.postSignal("onAnimationDone"); +} + +// get the index of the closest node on the specified path +function BadBot::getClosestNodeOnPath(%this, %path) +{ + if(isObject(%path) && %path.isMemberOfClass("SimSet") && (%numNodes = %path.getCount()) > 0) + { + %bestNode = 0; + %bestDist = VectorDist(%path.getObject(%bestNode).position, %this.position); + + for(%i=1; %i < %numNodes; %i++) + { + %node = %path.getObject(%i); + %dist = VectorDist(%node.position, %this.position); + + if(%dist < %bestDist) + { + %bestNode = %i; + %bestDist = %dist; + } + } + + return %bestNode; + } + return -1; +} + + +// send a chat message from the bot +function BadBot::say(%this, %message) +{ + chatMessageAll(%this, '\c3%1: %2', %this.getShapeName(), %message); +} + + +//=============================Global Utility================================== +function RandomPointOnCircle(%center, %radius) +{ + %randVec = (getRandom() - 0.5) SPC (getRandom() - 0.5) SPC "0"; + %randVec = VectorNormalize(%randVec); + %randVec = VectorScale(%randVec, %radius); + return VectorAdd(%center, %randVec); +} + +//===========================ScriptedBehavior Tasks============================= +/* + ScriptedBehavior Tasks are composed of four (optional) parts: + 1) precondition - this function should return a boolean indicating whether + or not the behavior should continue. If precondition returns true, the + rest of the behavior is evaluated. If precondition returns false, the + behavior will abort. + + There are two options for the evaluation of the precondition that can be + set in the editor: + ONCE - The precondition is run the first time the behavior becomes active + TICK - The precondition is run each time the behavior is ticked (if latent) + + 2) onEnter - This is called the first time the behavior is run if the + precondition was successful. onEnter does not use a return value. + + 3) onExit - This is called if the behavior reaches completion. onExit does + not use a return value. + + 4) behavior - This is the main behavior function, evaluated each tick. + behavior must return a status (SUCCES / FAILURE / RUNNING). +*/ + +//============================================================================== +// wander behavior task +//============================================================================== +function wanderTask::behavior(%this, %obj) +{ + // stop aiming at things + %obj.clearAim(); + + // if the bot has a tetherPoint, use that as the center of his wander area, + // otherwise use his current position + %basePoint = %obj.tetherPoint !$= "" ? %obj.tetherPoint : %obj.position; + + // move + %obj.moveTo(RandomPointOnCircle(%basePoint, 10)); + return SUCCESS; +} + + +//============================================================================== +// Move to closest node task +//============================================================================== +function moveToClosestNodeTask::precondition(%this, %obj) +{ + // check that the object has a path to folow + return isObject(%obj.path); +} + +function moveToClosestNodeTask::onEnter(%this, %obj) +{ + // stop aiming + %obj.clearAim(); +} + +function moveToClosestNodeTask::behavior(%this, %obj) +{ + // get the closest node + %obj.currentNode = %obj.getClosestNodeOnPath(%obj.path); + + // move toward it + %obj.moveTo(patrolPath.getObject(%obj.currentNode)); + return SUCCESS; +} + +//============================================================================== +// Patrol behavior task +//============================================================================== +function patrolTask::precondition(%this, %obj) +{ + // the bot needs a path object + return isObject(%obj.path); +} + +function patrolTask::onEnter(%this, %obj) +{ + // stop aiming + %obj.clearAim(); +} + +function patrolTask::behavior(%this, %obj) +{ + // hook into the standard AIPlayer path following + %obj.moveToNextNode(); + return SUCCESS; +} + +//============================================================================= +// findHealth task +//============================================================================= +function findHealthTask::behavior(%this, %obj) +{ + // get the objects datablock + %db = %obj.dataBlock; + + // do a container search for items + initContainerRadiusSearch( %obj.position, %db.findItemRange, %db.itemObjectTypes ); + while ( (%item = containerSearchNext()) != 0 ) + { + // filter out irrelevant items + if(%item.dataBlock.category !$= "Health" || !%item.isEnabled() || %item.isHidden()) + continue; + + // check that the item is within the bots view cone + if(%obj.checkInFov(%item, %db.visionFov)) + { + // set the targetItem field on the bot + %obj.targetItem = %item; + break; + } + } + + return isObject(%obj.targetItem) ? SUCCESS : FAILURE; +} + +//============================================================================= +// getHealth task +//============================================================================= +function getHealthTask::precondition(%this, %obj) +{ + // check that we have a valid health item to go for + return (isObject(%obj.targetItem) && %obj.targetItem.isEnabled() && !%obj.targetItem.isHidden()); +} + +function getHealthTask::onEnter(%this, %obj) +{ + // move to the item + %obj.moveTo(%obj.targetItem.position); +} + +function getHealthTask::behavior(%this, %obj) +{ + // succeed when we reach the item + if(!%obj.atDestination) + return RUNNING; + + return SUCCESS; +} + +//============================================================================= +// pickTarget task +//============================================================================= +function pickTargetTask::precondition(%this, %obj) +{ + // decide if we should pick a new target or keep the old one + if(isObject(%obj.targetObject)) + { + return (VectorDist(%obj, %obj.targetObject) > %obj.dataBlock.visionRange || + getRandom() < %obj.dataBlock.switchTargetProbability); + } + + return true; +} + +function pickTargetTask::behavior(%this, %obj) +{ + %obj.targetObject = -1; + %db = %obj.dataBlock; + + // container search for other players + initContainerRadiusSearch( %obj.position, %db.VisionRange, %db.targetObjectTypes ); + while ( (%target = containerSearchNext()) != 0 ) + { + // don't target ourself, dead players or god. + if(%target == %obj || !%target.isEnabled() || %target.isGod) + continue; + + // Check that the target is within the bots view cone + if(%obj.checkInFov(%target, %db.visionFov)) + { + // set the targetObject + %obj.targetObject = %target; + break; + } + } + + return SUCCESS; +} + +//============================================================================= +// aimAtTargetTask +//============================================================================= +function aimAtTargetTask::precondition(%this, %obj) +{ + // need to be alive and have a target + return isObject(%obj.targetObject) && %obj.isEnabled(); +} + +function aimAtTargetTask::behavior(%this, %obj) +{ + // calculate an aim offset + %targetPos = %obj.targetObject.getWorldBoxCenter(); + %weaponImage = %obj.getMountedImage($WeaponSlot); + %projectile = %weaponImage.projectile; + %correction = "0 0 1"; + if(isObject(%projectile)) + { + // simple target leading approximation (not for ballistics) + %targetDist = VectorDist(%targetPos, %obj.position); + %bulletVel = %projectile.muzzleVelocity; + %targetVel = %obj.targetObject.getVelocity(); + %correction = VectorAdd(%correction, VectorScale( %targetVel, (%targetDist / %bulletVel) )); + } + %obj.setAimObject(%obj.targetObject, %correction); + + return SUCCESS; +} + +//============================================================================= +// shootAtTargetTask +//============================================================================= +function shootAtTargetTask::precondition(%this, %obj) +{ + return isObject(%obj.targetObject) && + %obj.checkInLos(%obj.targetObject) && + VectorDot(VectorNormalize(VectorSub(%obj.getAimLocation(), %obj.position)), %obj.getForwardVector()) > 0.9 && + %obj.getImageAmmo($WeaponSlot); +} + +function shootAtTargetTask::behavior(%this, %obj) +{ + if(!isEventPending(%obj.triggerSchedule)) + { + %obj.setImageTrigger($WeaponSlot, true); + %burstLength = %obj.dataBlock.burstLength[%obj.getMountedImage($WeaponSlot).item.description]; + %obj.triggerSchedule = %obj.schedule(%burstLength, setImageTrigger, $WeaponSlot, false); + } + + return SUCCESS; +} + + +//============================================================================= +// combatMoveTask +//============================================================================= +function combatMoveTask::behavior(%this, %obj) +{ + %image = %obj.getMountedImage($WeaponSlot); + %db = %obj.getDatablock(); + %optimalRange = %db.optimalRange[%image.item.description]; + %currentRange = VectorDist(%obj.position, %obj.targetObject.position); + %rangeDelta = %currentRange - %optimalRange; + + %moveVec = "0 0 0"; + %fwd = %obj.getForwardVector(); + %right = %obj.getRightVector(); + + // forward / back to stay in range + if(mAbs(%rangeDelta) > %db.rangeTolerance) + %moveVec = VectorScale(%fwd, %rangeDelta); + + // random side strafe + %moveVec = VectorAdd(%moveVec, VectorScale(%right, 5 * (getRandom(0,2) - 1))); + + %obj.moveTo(VectorAdd(%obj.position, %moveVec)); + + return SUCCESS; +} diff --git a/Templates/Modules/badBehaviour/scripts/server/behaviorTreeManager.cs b/Templates/Modules/badBehaviour/scripts/server/behaviorTreeManager.cs new file mode 100644 index 0000000000..dc51db5758 --- /dev/null +++ b/Templates/Modules/badBehaviour/scripts/server/behaviorTreeManager.cs @@ -0,0 +1,138 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2014 Guy Allard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//----------------------------------------------------------------------------- + +function BehaviorTreeManager::create() +{ + if(isObject(BehaviorTreeManager)) + BehaviorTreeManager.delete(); + + if(isObject(ServerGroup)) + pushInstantGroup(ServerGroup); + + new ScriptObject(BehaviorTreeManager); + + if(isObject(ServerGroup)) + popInstantGroup(); +} + +function BehaviorTreeManager::onAdd(%this) +{ + if(isObject(BehaviorTreeGroup)) + BehaviorTreeGroup.delete(); + + if(isObject(ActiveBehaviorTreeGroup)) + ActiveBehaviorTreeGroup.delete(); + + new SimGroup(BehaviorTreeGroup); + new SimGroup(ActiveBehaviorTreeGroup); + + %this.loadTrees(); +} + +function BehaviorTreeManager::onRemove(%this) +{ + if(isObject(BehaviorTreeGroup)) + BehaviorTreeGroup.delete(); + + if(isObject(ActiveBehaviorTreeGroup)) + ActiveBehaviorTreeGroup.delete(); +} + +function BehaviorTreeManager::loadTrees(%this) +{ + if(!isDirectory("./behaviorTrees")) + return; + + pushInstantGroup(BehaviorTreeGroup); + + %pattern = "./behaviorTrees/*.cs"; + %file = findFirstFile( %pattern ); + if ( %file $= "" ) + { + // Try for DSOs next. + %pattern = "./behaviorTrees/*.cs.dso"; + %file = findFirstFile( %pattern ); + } + + while( %file !$= "" ) + { + exec( %file ); + %file = findNextFile( %pattern ); + } + + popInstantGroup(); +} + +function BehaviorTreeManager::createTree(%this, %obj, %tree) +{ + if(!isObject(%obj)) + { + error("BehaviorTreeManager::assignTree - object does not exist"); + return -1; + } + + if(!BehaviorTreeGroup.isMember(%tree)) + { + error("BehaviorTreeManager::assignTree - tree is not a member of BehaviorTreeGroup"); + return -1; + } + + pushInstantGroup(ActiveBehaviorTreeGroup); + %behaviorTree = new BehaviorTreeRunner() { + rootNode = %tree; + ownerObject = %obj; + }; + popInstantGroup(); + + return %behaviorTree; +} + +function BehaviorTreeManager::onBehaviorTreeEditor(%this, %val) +{ + if(%val) + onBehaviorTreeEditorStart(); + else + onBehaviorTreeEditorStop(); +} + +// give an object a behavior tree +function SimObject::setBehavior(%this, %tree, %frequency) +{ + if(isObject(%this.behaviorTree)) + %this.behaviorTree.rootNode = %tree; + else + %this.behaviorTree = BehaviorTreeManager.createTree(%this, %tree); + + if(%frequency) + %this.behaviorTree.frequency = %frequency; +} + + +// stop running a behavior tree on an object +function SimObject::clearBehavior(%this) +{ + if(isObject(%this.behaviorTree)) + %this.behaviorTree.clear(); +} + +BehaviorTreeManager::create(); + diff --git a/Templates/Modules/badBehaviour/scripts/server/behaviorTrees/botMatchTree.cs b/Templates/Modules/badBehaviour/scripts/server/behaviorTrees/botMatchTree.cs new file mode 100644 index 0000000000..7e8dd53823 --- /dev/null +++ b/Templates/Modules/badBehaviour/scripts/server/behaviorTrees/botMatchTree.cs @@ -0,0 +1,142 @@ +//--- OBJECT WRITE BEGIN --- +new Root(botMatchTree) { + canSave = "1"; + canSaveDynamicFields = "1"; + + new Selector() { + canSave = "1"; + canSaveDynamicFields = "1"; + + new Sequence() { + internalName = "run the match"; + canSave = "1"; + canSaveDynamicFields = "1"; + + new ScriptEval() { + behaviorScript = "if(isObject(%obj.botGroup))\n %obj.botGroup.delete();\n\n%obj.botGroup = new SimGroup();\n%obj.countdown = 5;\n%obj.numBotsToSpawn = %obj.numBots;"; + defaultReturnStatus = "SUCCESS"; + internalName = "init"; + canSave = "1"; + canSaveDynamicFields = "1"; + }; + new Sequence() { + internalName = "countdown"; + canSave = "1"; + canSaveDynamicFields = "1"; + + new Ticker() { + frequencyMs = "1000"; + canSave = "1"; + canSaveDynamicFields = "1"; + + new Loop() { + numLoops = "5"; + terminationPolicy = "ON_FAILURE"; + canSave = "1"; + canSaveDynamicFields = "1"; + + new ScriptEval() { + behaviorScript = "centerPrintAll(\"BotMatch in\" SPC %obj.countdown, 2);\n%obj.countdown --;"; + defaultReturnStatus = "SUCCESS"; + internalName = "countdown message"; + canSave = "1"; + canSaveDynamicFields = "1"; + }; + }; + }; + new ScriptEval() { + behaviorScript = "centerPrintAll(\"GO!\",1);"; + defaultReturnStatus = "SUCCESS"; + internalName = "lets go"; + canSave = "1"; + canSaveDynamicFields = "1"; + }; + }; + new SucceedAlways() { + canSave = "1"; + canSaveDynamicFields = "1"; + + new Loop() { + numLoops = "0"; + terminationPolicy = "ON_FAILURE"; + canSave = "1"; + canSaveDynamicFields = "1"; + + new Sequence() { + internalName = "spawn bots"; + canSave = "1"; + canSaveDynamicFields = "1"; + + new ScriptEval() { + behaviorScript = "// pick a marker to spawn at\n%spawnpoint = PatrolPath.getRandom();\n\n// create the bot\n%bot = BadBot::spawn(\"\", %spawnpoint);\n\n// set its behavior\n%bot.setbehavior(BotTree, $BotTickFrequency);\n\n// add it to the botgroup\n%obj.botGroup.add(%bot);\n\n// keep track of the current bot\n%obj.currentBot = %bot;\n\n// decrement the number of bots left to spawn\n%obj.numBotsToSpawn --;"; + defaultReturnStatus = "SUCCESS"; + internalName = "spawn one bot"; + canSave = "1"; + canSaveDynamicFields = "1"; + }; + new RandomSelector() { + internalName = "pick a weapon"; + canSave = "1"; + canSaveDynamicFields = "1"; + + new ScriptEval() { + behaviorScript = "%obj.currentBot.use(Ryder);"; + defaultReturnStatus = "SUCCESS"; + internalName = "Ryder"; + canSave = "1"; + canSaveDynamicFields = "1"; + }; + new ScriptEval() { + behaviorScript = "%obj.currentBot.use(Lurker);"; + defaultReturnStatus = "SUCCESS"; + internalName = "Lurker"; + canSave = "1"; + canSaveDynamicFields = "1"; + }; + }; + new ScriptEval() { + behaviorScript = "if(%obj.numBotsToSpawn == 0) return FAILURE;"; + defaultReturnStatus = "SUCCESS"; + internalName = "check if more bots to spawn"; + canSave = "1"; + canSaveDynamicFields = "1"; + }; + }; + }; + }; + new FailAlways() { + canSave = "1"; + canSaveDynamicFields = "1"; + + new WaitForSignal() { + signalName = "onBotmatchCancel"; + timeoutMs = "0"; + internalName = "stop on cancel signal"; + canSave = "1"; + canSaveDynamicFields = "1"; + }; + }; + }; + new Sequence() { + internalName = "end the match"; + canSave = "1"; + canSaveDynamicFields = "1"; + + new ScriptEval() { + behaviorScript = "%obj.botGroup.delete();"; + defaultReturnStatus = "SUCCESS"; + internalName = "remove bots"; + canSave = "1"; + canSaveDynamicFields = "1"; + }; + new ScriptEval() { + behaviorScript = "%obj.behaviorTree.schedule(10, stop);"; + defaultReturnStatus = "SUCCESS"; + internalName = "stop"; + canSave = "1"; + canSaveDynamicFields = "1"; + }; + }; + }; +}; +//--- OBJECT WRITE END --- diff --git a/Templates/Modules/badBehaviour/scripts/server/behaviorTrees/botTree.cs b/Templates/Modules/badBehaviour/scripts/server/behaviorTrees/botTree.cs new file mode 100644 index 0000000000..b48967718e --- /dev/null +++ b/Templates/Modules/badBehaviour/scripts/server/behaviorTrees/botTree.cs @@ -0,0 +1,70 @@ +//--- OBJECT WRITE BEGIN --- +new Root(BotTree) { + canSave = "1"; + canSaveDynamicFields = "1"; + + new Parallel() { + returnPolicy = "REQUIRE_ALL"; + canSave = "1"; + canSaveDynamicFields = "1"; + + new Ticker() { + frequencyMs = "1000"; + canSave = "1"; + canSaveDynamicFields = "1"; + + new Loop() { + numLoops = "0"; + terminationPolicy = "ON_FAILURE"; + canSave = "1"; + canSaveDynamicFields = "1"; + + new ScriptedBehavior() { + preconditionMode = "ONCE"; + internalName = "look for enemy"; + class = "pickTargetTask"; + canSave = "1"; + canSaveDynamicFields = "1"; + }; + }; + }; + new Loop() { + numLoops = "0"; + terminationPolicy = "ON_FAILURE"; + canSave = "1"; + canSaveDynamicFields = "1"; + + new ActiveSelector() { + recheckFrequency = "1000"; + canSave = "1"; + canSaveDynamicFields = "1"; + + new SubTree() { + subTreeName = "getHealthTree"; + internalName = "get health"; + canSave = "1"; + canSaveDynamicFields = "1"; + }; + new SubTree() { + subTreeName = "combatTree"; + internalName = "combat"; + canSave = "1"; + canSaveDynamicFields = "1"; + }; + new SubTree() { + subTreeName = "PatrolTree"; + internalName = "patrol"; + canSave = "1"; + canSaveDynamicFields = "1"; + }; + new SubTree() { + subTreeName = "WanderTree"; + internalName = "wander"; + canSave = "1"; + canSaveDynamicFields = "1"; + }; + }; + }; + }; +}; +//--- OBJECT WRITE END --- diff --git a/Templates/Modules/badBehaviour/scripts/server/behaviorTrees/combatTree.cs b/Templates/Modules/badBehaviour/scripts/server/behaviorTrees/combatTree.cs new file mode 100644 index 0000000000..81678e39f4 --- /dev/null +++ b/Templates/Modules/badBehaviour/scripts/server/behaviorTrees/combatTree.cs @@ -0,0 +1,99 @@ +//--- OBJECT WRITE BEGIN --- +new Root(CombatTree) { + canSave = "1"; + canSaveDynamicFields = "1"; + + new Sequence() { + canSave = "1"; + canSaveDynamicFields = "1"; + + new ScriptEval() { + behaviorScript = "if (isObject(%obj.targetObject) && %obj.targetObject.isEnabled()) return SUCCESS;"; + defaultReturnStatus = "FAILURE"; + canSave = "1"; + canSaveDynamicFields = "1"; + }; + new Parallel() { + returnPolicy = "REQUIRE_ALL"; + canSave = "1"; + canSaveDynamicFields = "1"; + + new Loop() { + numLoops = "0"; + terminationPolicy = "ON_FAILURE"; + canSave = "1"; + canSaveDynamicFields = "1"; + + new Sequence() { + canSave = "1"; + canSaveDynamicFields = "1"; + + new ScriptedBehavior() { + preconditionMode = "ONCE"; + internalName = "combatMoveTask"; + class = "combatMoveTask"; + canSave = "1"; + canSaveDynamicFields = "1"; + }; + new RandomWait() { + waitMinMs = "500"; + waitMaxMs = "1000"; + canSave = "1"; + canSaveDynamicFields = "1"; + }; + }; + }; + new Loop() { + numLoops = "0"; + terminationPolicy = "ON_FAILURE"; + canSave = "1"; + canSaveDynamicFields = "1"; + + new Ticker() { + frequencyMs = "500"; + canSave = "1"; + canSaveDynamicFields = "1"; + + new ScriptedBehavior() { + preconditionMode = "TICK"; + internalName = "aim"; + class = "aimAtTargetTask"; + canSave = "1"; + canSaveDynamicFields = "1"; + }; + }; + }; + new Loop() { + numLoops = "0"; + terminationPolicy = "ON_FAILURE"; + canSave = "1"; + canSaveDynamicFields = "1"; + + new SucceedAlways() { + canSave = "1"; + canSaveDynamicFields = "1"; + + new Sequence() { + canSave = "1"; + canSaveDynamicFields = "1"; + + new ScriptedBehavior() { + preconditionMode = "ONCE"; + internalName = "fire"; + class = "shootAtTargetTask"; + canSave = "1"; + canSaveDynamicFields = "1"; + }; + new RandomWait() { + waitMinMs = "500"; + waitMaxMs = "1000"; + canSave = "1"; + canSaveDynamicFields = "1"; + }; + }; + }; + }; + }; + }; +}; +//--- OBJECT WRITE END --- diff --git a/Templates/Modules/badBehaviour/scripts/server/behaviorTrees/followTree.cs b/Templates/Modules/badBehaviour/scripts/server/behaviorTrees/followTree.cs new file mode 100644 index 0000000000..9bda795745 --- /dev/null +++ b/Templates/Modules/badBehaviour/scripts/server/behaviorTrees/followTree.cs @@ -0,0 +1,26 @@ +//--- OBJECT WRITE BEGIN --- +new Root(FollowTree) { + canSave = "1"; + canSaveDynamicFields = "1"; + + new Sequence() { + canSave = "1"; + canSaveDynamicFields = "1"; + + new ScriptEval() { + behaviorScript = "return isObject(%obj.followObject) ? SUCCESS : FAILURE;\n"; + defaultReturnStatus = "SUCCESS"; + internalName = "has object to follow?"; + canSave = "1"; + canSaveDynamicFields = "1"; + }; + + new FollowBehaviorAction() { + preconditionMode = "TICK"; + internalName = "follow the object"; + canSave = "1"; + canSaveDynamicFields = "1"; + }; + }; +}; +//--- OBJECT WRITE END --- diff --git a/Templates/Modules/badBehaviour/scripts/server/behaviorTrees/getHealthTree.cs b/Templates/Modules/badBehaviour/scripts/server/behaviorTrees/getHealthTree.cs new file mode 100644 index 0000000000..3dfca5b16e --- /dev/null +++ b/Templates/Modules/badBehaviour/scripts/server/behaviorTrees/getHealthTree.cs @@ -0,0 +1,33 @@ +//--- OBJECT WRITE BEGIN --- +new Root(getHealthTree) { + canSave = "1"; + canSaveDynamicFields = "1"; + + new Sequence() { + canSave = "1"; + canSaveDynamicFields = "1"; + + new ScriptEval() { + behaviorScript = "if (%obj.getDamagePercent() > 0.75) return SUCCESS;"; + defaultReturnStatus = "FAILURE"; + internalName = "need health?"; + canSave = "1"; + canSaveDynamicFields = "1"; + }; + new ScriptedBehavior() { + preconditionMode = "ONCE"; + internalName = "look for health"; + class = "findHealthTask"; + canSave = "1"; + canSaveDynamicFields = "1"; + }; + new ScriptedBehavior() { + preconditionMode = "TICK"; + internalName = "get health"; + class = "getHealthTask"; + canSave = "1"; + canSaveDynamicFields = "1"; + }; + }; +}; +//--- OBJECT WRITE END --- diff --git a/Templates/Modules/badBehaviour/scripts/server/behaviorTrees/patrolTree.cs b/Templates/Modules/badBehaviour/scripts/server/behaviorTrees/patrolTree.cs new file mode 100644 index 0000000000..05eb73ea6c --- /dev/null +++ b/Templates/Modules/badBehaviour/scripts/server/behaviorTrees/patrolTree.cs @@ -0,0 +1,50 @@ +//--- OBJECT WRITE BEGIN --- +new Root(PatrolTree) { + canSave = "1"; + canSaveDynamicFields = "1"; + + new Sequence() { + canSave = "1"; + canSaveDynamicFields = "1"; + + new ScriptedBehavior() { + preconditionMode = "ONCE"; + internalName = "Move to closest node on path"; + class = "moveToClosestNodeTask"; + canSave = "1"; + canSaveDynamicFields = "1"; + }; + new WaitForSignal() { + signalName = "onReachDestination"; + internalName = "onReachDestination"; + canSave = "1"; + canSaveDynamicFields = "1"; + }; + new Loop() { + numLoops = "0"; + terminationPolicy = "ON_FAILURE"; + canSave = "1"; + canSaveDynamicFields = "1"; + + new Sequence() { + canSave = "1"; + canSaveDynamicFields = "1"; + + new ScriptedBehavior() { + preconditionMode = "ONCE"; + internalName = "move along path"; + class = "patrolTask"; + canSave = "1"; + canSaveDynamicFields = "1"; + }; + new WaitForSignal() { + signalName = "onReachDestination"; + internalName = "onReachDestination"; + canSave = "1"; + canSaveDynamicFields = "1"; + }; + }; + }; + }; +}; +//--- OBJECT WRITE END --- diff --git a/Templates/Modules/badBehaviour/scripts/server/behaviorTrees/wanderTree.cs b/Templates/Modules/badBehaviour/scripts/server/behaviorTrees/wanderTree.cs new file mode 100644 index 0000000000..ab3330fdbe --- /dev/null +++ b/Templates/Modules/badBehaviour/scripts/server/behaviorTrees/wanderTree.cs @@ -0,0 +1,33 @@ +//--- OBJECT WRITE BEGIN --- +new Root(WanderTree) { + canSave = "1"; + canSaveDynamicFields = "1"; + + new Sequence() { + canSave = "1"; + canSaveDynamicFields = "1"; + + new ScriptedBehavior() { + preconditionMode = "ONCE"; + internalName = "move somewhere"; + class = "wanderTask"; + canSave = "1"; + canSaveDynamicFields = "1"; + }; + new WaitForSignal() { + signalName = "onReachDestination"; + timeoutMs = "0"; + internalName = "wait until there"; + canSave = "1"; + canSaveDynamicFields = "1"; + }; + new RandomWait() { + waitMinMs = "0"; + waitMaxMs = "1000"; + internalName = "pause"; + canSave = "1"; + canSaveDynamicFields = "1"; + }; + }; +}; +//--- OBJECT WRITE END --- diff --git a/Templates/Modules/badBehaviour/scripts/server/botMatch.cs b/Templates/Modules/badBehaviour/scripts/server/botMatch.cs new file mode 100644 index 0000000000..951e6ad182 --- /dev/null +++ b/Templates/Modules/badBehaviour/scripts/server/botMatch.cs @@ -0,0 +1,50 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2014 Guy Allard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//----------------------------------------------------------------------------- + +$BotMatchTickFrequency = 250; + +// start a bot match +function botMatch(%numBots) +{ + // Avoid having lots of dead bodies lying around. + $CorpseTimeoutValue = 2000; + + // script object to attach the BT to + if(!isObject(BotMatch)) + new ScriptObject(botMatch); + + // number of bots that will be spawned + botMatch.numBots = %numBots; + + // set the behavior tree + botMatch.setBehavior(botMatchTree, $BotMatchTickFrequency); +} + + +// cancel the match +function cancelBotmatch() +{ + // post the signal to the behavior tree + if(isObject(botMatch)) + botMatch.behaviorTree.postSignal("onBotmatchCancel"); +} + diff --git a/Tools/CMake/modules/module_badBehavior.cmake b/Tools/CMake/modules/module_badBehavior.cmake new file mode 100644 index 0000000000..744bc72fee --- /dev/null +++ b/Tools/CMake/modules/module_badBehavior.cmake @@ -0,0 +1,32 @@ +# ----------------------------------------------------------------------------- +# Copyright (c) 2014 Guy Allard +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to +# deal in the Software without restriction, including without limitation the +# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +# sell copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +# IN THE SOFTWARE. +# ----------------------------------------------------------------------------- + +# Navigation module +option(TORQUE_BADBEHAVIOR "Enable BadBehavior module" ON) + +if(TORQUE_BADBEHAVIOR) + #addDef( "TORQUE_BADBEHAVIOR_ENABLED" ) + + # files + addPathRec( "${srcDir}/BadBehavior" ) +endif(TORQUE_BADBEHAVIOR) +