You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1936 lines
68 KiB
1936 lines
68 KiB
// NextBotBehaviorEngine.h |
|
// Behavioral system constructed from Actions |
|
// Author: Michael Booth, April 2006 |
|
//========= Copyright Valve Corporation, All rights reserved. ============// |
|
|
|
#ifndef _BEHAVIOR_ENGINE_H_ |
|
#define _BEHAVIOR_ENGINE_H_ |
|
|
|
#include "fmtstr.h" |
|
#include "NextBotEventResponderInterface.h" |
|
#include "NextBotContextualQueryInterface.h" |
|
#include "NextBotDebug.h" |
|
#include "tier0/vprof.h" |
|
|
|
|
|
//#define DEBUG_BEHAVIOR_MEMORY |
|
extern ConVar NextBotDebugHistory; |
|
|
|
/** |
|
* Notes: |
|
* |
|
* By using return results to cause transitions, we ensure the atomic-ness |
|
* of these transitions. For instance, it is not possible to change to a |
|
* new Action and continue execution of code in the current Action. |
|
* |
|
* Creation and deletion of Actions during transitions allows passing of |
|
* type-safe arguments between Actions via constructors. |
|
* |
|
* Events are propagated to each Action in the hierarchy. If an |
|
* action is suspended for another action, it STILL RECEIVES EVENTS |
|
* that are not handled by the events "above it" in the suspend stack. |
|
* In other words, the active Action gets the first response, and if it |
|
* returns CONTINUE, the Action buried beneath it can process it, |
|
* and so on deeper into the stack of suspended Actions. |
|
* |
|
* About events: |
|
* It is not possible to have event handlers instantaneously change |
|
* state upon return due to out-of-order and recurrence issues, not |
|
* to mention deleting the state out from under itself. Therefore, |
|
* events return DESIRED results, and the highest priority result |
|
* is executed at the next Update(). |
|
* |
|
* About buried Actions causing SUSPEND_FOR results: |
|
* If a buried Action reacts to an event by returning a SUSPEND_FOR, |
|
* the new interrupting Action is put at the TOP of the stack, burying |
|
* whatever Action was there. |
|
* |
|
*/ |
|
|
|
|
|
// forward declaration |
|
template < typename Actor > class Action; |
|
|
|
/** |
|
* The possible consequences of an Action |
|
*/ |
|
enum ActionResultType |
|
{ |
|
CONTINUE, // continue executing this action next frame - nothing has changed |
|
CHANGE_TO, // change actions next frame |
|
SUSPEND_FOR, // put the current action on hold for the new action |
|
DONE, // this action has finished, resume suspended action |
|
SUSTAIN, // for use with event handlers - a way to say "It's important to keep doing what I'm doing" |
|
}; |
|
|
|
|
|
//---------------------------------------------------------------------------------------------- |
|
/** |
|
* Actions and Event processors return results derived from this class. |
|
* Do not assemble this yourself - use the Continue(), ChangeTo(), Done(), and SuspendFor() |
|
* methods within Action. |
|
*/ |
|
template < typename Actor > |
|
struct IActionResult |
|
{ |
|
IActionResult( ActionResultType type = CONTINUE, Action< Actor > *action = NULL, const char *reason = NULL ) |
|
{ |
|
m_type = type; |
|
m_action = action; |
|
m_reason = reason; |
|
} |
|
|
|
bool IsDone( void ) const |
|
{ |
|
return ( m_type == DONE ); |
|
} |
|
|
|
bool IsContinue( void ) const |
|
{ |
|
return ( m_type == CONTINUE ); |
|
} |
|
|
|
bool IsRequestingChange( void ) const |
|
{ |
|
return ( m_type == CHANGE_TO || m_type == SUSPEND_FOR || m_type == DONE ); |
|
} |
|
|
|
const char *GetTypeName( void ) const |
|
{ |
|
switch ( m_type ) |
|
{ |
|
case CHANGE_TO: return "CHANGE_TO"; |
|
case SUSPEND_FOR: return "SUSPEND_FOR"; |
|
case DONE: return "DONE"; |
|
case SUSTAIN: return "SUSTAIN"; |
|
|
|
default: |
|
case CONTINUE: return "CONTINUE"; |
|
} |
|
} |
|
|
|
ActionResultType m_type; |
|
Action< Actor > *m_action; |
|
const char *m_reason; |
|
}; |
|
|
|
|
|
//---------------------------------------------------------------------------------------------- |
|
/** |
|
* When an Action is executed it returns this result. |
|
* Do not assemble this yourself - use the Continue(), ChangeTo(), Done(), and SuspendFor() |
|
* methods within Action. |
|
*/ |
|
template < typename Actor > |
|
struct ActionResult : public IActionResult< Actor > |
|
{ |
|
// this is derived from IActionResult to ensure that ActionResult and EventDesiredResult cannot be silently converted |
|
ActionResult( ActionResultType type = CONTINUE, Action< Actor > *action = NULL, const char *reason = NULL ) : IActionResult< Actor >( type, action, reason ) { } |
|
}; |
|
|
|
|
|
//---------------------------------------------------------------------------------------------- |
|
/** |
|
* When an event is processed, it returns this DESIRED result, |
|
* which may or MAY NOT happen, depending on other event results |
|
* that occur simultaneously. |
|
* Do not assemble this yourself - use the TryContinue(), TryChangeTo(), TryDone(), TrySustain(), |
|
* and TrySuspendFor() methods within Action. |
|
*/ |
|
enum EventResultPriorityType |
|
{ |
|
RESULT_NONE, // no result |
|
RESULT_TRY, // use this result, or toss it out, either is ok |
|
RESULT_IMPORTANT, // try extra-hard to use this result |
|
RESULT_CRITICAL // this result must be used - emit an error if it can't be |
|
}; |
|
|
|
template < typename Actor > |
|
struct EventDesiredResult : public IActionResult< Actor > |
|
{ |
|
EventDesiredResult( ActionResultType type = CONTINUE, Action< Actor > *action = NULL, EventResultPriorityType priority = RESULT_TRY, const char *reason = NULL ) : IActionResult< Actor >( type, action, reason ) |
|
{ |
|
m_priority = priority; |
|
} |
|
|
|
EventResultPriorityType m_priority; |
|
}; |
|
|
|
|
|
//------------------------------------------------------------------------------------------------------------- |
|
//------------------------------------------------------------------------------------------------------------- |
|
/** |
|
* A Behavior is the root of an Action hierarchy as well as its container/manager. |
|
* Instantiate a Behavior with the root Action of your behavioral system, and |
|
* call Behavior::Update() to drive it. |
|
*/ |
|
template < typename Actor > |
|
class Behavior : public INextBotEventResponder, public IContextualQuery |
|
{ |
|
public: |
|
DECLARE_CLASS( Behavior, INextBotEventResponder ); |
|
|
|
Behavior( Action< Actor > *initialAction, const char *name = "" ) : m_name( "%s", name ) |
|
{ |
|
m_action = initialAction; |
|
m_me = NULL; |
|
} |
|
|
|
virtual ~Behavior() |
|
{ |
|
if ( m_me && m_action ) |
|
{ |
|
// allow all currently active Actions to end |
|
m_action->InvokeOnEnd( m_me, this, NULL ); |
|
m_me = NULL; |
|
} |
|
|
|
// dig down to the bottom of the action stack and delete |
|
// that, so we don't leak action memory since action |
|
// destructors intentionally don't delete actions |
|
// "buried" underneath them. |
|
Action< Actor > *bottomAction; |
|
for( bottomAction = m_action; bottomAction && bottomAction->m_buriedUnderMe; bottomAction = bottomAction->m_buriedUnderMe ) |
|
; |
|
|
|
if ( bottomAction ) |
|
{ |
|
delete bottomAction; |
|
} |
|
|
|
// delete any dead Actions |
|
m_deadActionVector.PurgeAndDeleteElements(); |
|
} |
|
|
|
/** |
|
* Reset this Behavior with the given Action. If this Behavior |
|
* was already running, this will delete all current Actions and |
|
* restart the Behavior with the new one. |
|
*/ |
|
void Reset( Action< Actor > *action ) |
|
{ |
|
if ( m_me && m_action ) |
|
{ |
|
// allow all currently active Actions to end |
|
m_action->InvokeOnEnd( m_me, this, NULL ); |
|
m_me = NULL; |
|
} |
|
|
|
// find "bottom" action (see comment in destructor) |
|
Action< Actor > *bottomAction; |
|
for( bottomAction = m_action; bottomAction && bottomAction->m_buriedUnderMe; bottomAction = bottomAction->m_buriedUnderMe ) |
|
; |
|
|
|
if ( bottomAction ) |
|
{ |
|
delete bottomAction; |
|
} |
|
|
|
// delete any dead Actions |
|
m_deadActionVector.PurgeAndDeleteElements(); |
|
|
|
m_action = action; |
|
} |
|
|
|
/** |
|
* Return true if this Behavior contains no actions |
|
*/ |
|
bool IsEmpty( void ) const |
|
{ |
|
return m_action == NULL; |
|
} |
|
|
|
/** |
|
* Execute this Behavior |
|
*/ |
|
void Update( Actor *me, float interval ) |
|
{ |
|
if ( me == NULL || IsEmpty() ) |
|
{ |
|
return; |
|
} |
|
|
|
m_me = me; |
|
|
|
m_action = m_action->ApplyResult( me, this, m_action->InvokeUpdate( me, this, interval ) ); |
|
|
|
if ( m_action && me->IsDebugging( NEXTBOT_BEHAVIOR ) ) |
|
{ |
|
CFmtStr msg; |
|
me->DisplayDebugText( msg.sprintf( "%s: %s", GetName(), m_action->DebugString() ) ); |
|
} |
|
|
|
// delete any dead Actions |
|
m_deadActionVector.PurgeAndDeleteElements(); |
|
} |
|
|
|
/** |
|
* If this Behavior has not been Update'd in a long time, |
|
* call Resume() to let the system know its internal state may |
|
* be out of date. |
|
*/ |
|
void Resume( Actor *me ) |
|
{ |
|
if ( me == NULL || IsEmpty() ) |
|
{ |
|
return; |
|
} |
|
|
|
m_action = m_action->ApplyResult( me, this, m_action->OnResume( me, NULL ) ); |
|
|
|
if ( m_action && me->IsDebugging( NEXTBOT_BEHAVIOR ) ) |
|
{ |
|
CFmtStr msg; |
|
me->DisplayDebugText( msg.sprintf( "%s: %s", GetName(), m_action->DebugString() ) ); |
|
} |
|
} |
|
|
|
/** |
|
* Use this method to destroy Actions used by this Behavior. |
|
* We cannot delete Actions in-line since Action updates can potentially |
|
* invoke event responders which will then use potentially deleted |
|
* Action pointers, causing memory corruption. |
|
* Instead, we will collect the dead Actions and delete them at the |
|
* end of Update(). |
|
*/ |
|
void DestroyAction( Action< Actor > *dead ) |
|
{ |
|
m_deadActionVector.AddToTail( dead ); |
|
} |
|
|
|
const char *GetName( void ) const |
|
{ |
|
return m_name; |
|
} |
|
|
|
// INextBotEventResponder propagation ---------------------------------------------------------------------- |
|
virtual INextBotEventResponder *FirstContainedResponder( void ) const |
|
{ |
|
return m_action; |
|
} |
|
|
|
virtual INextBotEventResponder *NextContainedResponder( INextBotEventResponder *current ) const |
|
{ |
|
return NULL; |
|
} |
|
|
|
// IContextualQuery propagation ---------------------------------------------------------------------------- |
|
virtual QueryResultType ShouldPickUp( const INextBot *me, CBaseEntity *item ) const // if the desired item was available right now, should we pick it up? |
|
{ |
|
QueryResultType result = ANSWER_UNDEFINED; |
|
|
|
if ( m_action ) |
|
{ |
|
// find innermost child action |
|
Action< Actor > *action; |
|
for( action = m_action; action->m_child; action = action->m_child ) |
|
; |
|
|
|
// work our way through our containers |
|
while( action && result == ANSWER_UNDEFINED ) |
|
{ |
|
Action< Actor > *containingAction = action->m_parent; |
|
|
|
// work our way up the stack |
|
while( action && result == ANSWER_UNDEFINED ) |
|
{ |
|
result = action->ShouldPickUp( me, item ); |
|
action = action->GetActionBuriedUnderMe(); |
|
} |
|
|
|
action = containingAction; |
|
} |
|
} |
|
|
|
return result; |
|
} |
|
|
|
virtual QueryResultType ShouldHurry( const INextBot *me ) const // are we in a hurry? |
|
{ |
|
QueryResultType result = ANSWER_UNDEFINED; |
|
|
|
if ( m_action ) |
|
{ |
|
// find innermost child action |
|
Action< Actor > *action; |
|
for( action = m_action; action->m_child; action = action->m_child ) |
|
; |
|
|
|
// work our way through our containers |
|
while( action && result == ANSWER_UNDEFINED ) |
|
{ |
|
Action< Actor > *containingAction = action->m_parent; |
|
|
|
// work our way up the stack |
|
while( action && result == ANSWER_UNDEFINED ) |
|
{ |
|
result = action->ShouldHurry( me ); |
|
action = action->GetActionBuriedUnderMe(); |
|
} |
|
|
|
action = containingAction; |
|
} |
|
} |
|
|
|
return result; |
|
} |
|
|
|
virtual QueryResultType ShouldRetreat( const INextBot *me ) const // is it time to retreat? |
|
{ |
|
QueryResultType result = ANSWER_UNDEFINED; |
|
|
|
if ( m_action ) |
|
{ |
|
// find innermost child action |
|
Action< Actor > *action; |
|
for( action = m_action; action->m_child; action = action->m_child ) |
|
; |
|
|
|
// work our way through our containers |
|
while( action && result == ANSWER_UNDEFINED ) |
|
{ |
|
Action< Actor > *containingAction = action->m_parent; |
|
|
|
// work our way up the stack |
|
while( action && result == ANSWER_UNDEFINED ) |
|
{ |
|
result = action->ShouldRetreat( me ); |
|
action = action->GetActionBuriedUnderMe(); |
|
} |
|
|
|
action = containingAction; |
|
} |
|
} |
|
|
|
return result; |
|
} |
|
|
|
virtual QueryResultType ShouldAttack( const INextBot *me, const CKnownEntity *them ) const // should we attack "them"? |
|
{ |
|
QueryResultType result = ANSWER_UNDEFINED; |
|
|
|
if ( m_action ) |
|
{ |
|
// find innermost child action |
|
Action< Actor > *action; |
|
for( action = m_action; action->m_child; action = action->m_child ) |
|
; |
|
|
|
// work our way through our containers |
|
while( action && result == ANSWER_UNDEFINED ) |
|
{ |
|
Action< Actor > *containingAction = action->m_parent; |
|
|
|
// work our way up the stack |
|
while( action && result == ANSWER_UNDEFINED ) |
|
{ |
|
result = action->ShouldAttack( me, them ); |
|
action = action->GetActionBuriedUnderMe(); |
|
} |
|
|
|
action = containingAction; |
|
} |
|
} |
|
|
|
return result; |
|
} |
|
|
|
virtual QueryResultType IsHindrance( const INextBot *me, CBaseEntity *blocker ) const // return true if we should wait for 'blocker' that is across our path somewhere up ahead. |
|
{ |
|
QueryResultType result = ANSWER_UNDEFINED; |
|
|
|
if ( m_action ) |
|
{ |
|
// find innermost child action |
|
Action< Actor > *action; |
|
for( action = m_action; action->m_child; action = action->m_child ) |
|
; |
|
|
|
// work our way through our containers |
|
while( action && result == ANSWER_UNDEFINED ) |
|
{ |
|
Action< Actor > *containingAction = action->m_parent; |
|
|
|
// work our way up the stack |
|
while( action && result == ANSWER_UNDEFINED ) |
|
{ |
|
result = action->IsHindrance( me, blocker ); |
|
action = action->GetActionBuriedUnderMe(); |
|
} |
|
|
|
action = containingAction; |
|
} |
|
} |
|
|
|
return result; |
|
} |
|
|
|
|
|
virtual Vector SelectTargetPoint( const INextBot *me, const CBaseCombatCharacter *subject ) const // given a subject, return the world space position we should aim at |
|
{ |
|
Vector result = vec3_origin; |
|
|
|
if ( m_action ) |
|
{ |
|
// find innermost child action |
|
Action< Actor > *action; |
|
for( action = m_action; action->m_child; action = action->m_child ) |
|
; |
|
|
|
// work our way through our containers |
|
while( action && result == vec3_origin ) |
|
{ |
|
Action< Actor > *containingAction = action->m_parent; |
|
|
|
// work our way up the stack |
|
while( action && result == vec3_origin ) |
|
{ |
|
result = action->SelectTargetPoint( me, subject ); |
|
action = action->GetActionBuriedUnderMe(); |
|
} |
|
|
|
action = containingAction; |
|
} |
|
} |
|
|
|
return result; |
|
} |
|
|
|
|
|
/** |
|
* Allow bot to approve of positions game movement tries to put him into. |
|
* This is most useful for bots derived from CBasePlayer that go through |
|
* the player movement system. |
|
*/ |
|
virtual QueryResultType IsPositionAllowed( const INextBot *me, const Vector &pos ) const |
|
{ |
|
QueryResultType result = ANSWER_UNDEFINED; |
|
|
|
if ( m_action ) |
|
{ |
|
// find innermost child action |
|
Action< Actor > *action; |
|
for( action = m_action; action->m_child; action = action->m_child ) |
|
; |
|
|
|
// work our way through our containers |
|
while( action && result == ANSWER_UNDEFINED ) |
|
{ |
|
Action< Actor > *containingAction = action->m_parent; |
|
|
|
// work our way up the stack |
|
while( action && result == ANSWER_UNDEFINED ) |
|
{ |
|
result = action->IsPositionAllowed( me, pos ); |
|
action = action->GetActionBuriedUnderMe(); |
|
} |
|
|
|
action = containingAction; |
|
} |
|
} |
|
|
|
return result; |
|
} |
|
|
|
|
|
|
|
virtual const CKnownEntity *SelectMoreDangerousThreat( const INextBot *me, const CBaseCombatCharacter *subject, const CKnownEntity *threat1, const CKnownEntity *threat2 ) const // return the more dangerous of the two threats, or NULL if we have no opinion |
|
{ |
|
const CKnownEntity *result = NULL; |
|
|
|
if ( m_action ) |
|
{ |
|
// find innermost child action |
|
Action< Actor > *action; |
|
for( action = m_action; action->m_child; action = action->m_child ) |
|
; |
|
|
|
// work our way through our containers |
|
while( action && result == NULL ) |
|
{ |
|
Action< Actor > *containingAction = action->m_parent; |
|
|
|
// work our way up the stack |
|
while( action && result == NULL ) |
|
{ |
|
result = action->SelectMoreDangerousThreat( me, subject, threat1, threat2 ); |
|
action = action->GetActionBuriedUnderMe(); |
|
} |
|
|
|
action = containingAction; |
|
} |
|
} |
|
|
|
return result; |
|
} |
|
|
|
|
|
private: |
|
Action< Actor > *m_action; |
|
|
|
#define MAX_NAME_LENGTH 32 |
|
CFmtStrN< MAX_NAME_LENGTH > m_name; |
|
|
|
Actor *m_me; |
|
|
|
CUtlVector< Action< Actor > * > m_deadActionVector; // completed Actions pending deletion |
|
}; |
|
|
|
|
|
//---------------------------------------------------------------------------------------------- |
|
/** |
|
* Something an Actor does. |
|
* Actions can contain Actions, representing the precise context of the Actor's behavior. |
|
* A system of Actions is contained within a Behavior, which acts as the manager |
|
* of the Action system. |
|
*/ |
|
template < typename Actor > |
|
class Action : public INextBotEventResponder, public IContextualQuery |
|
{ |
|
public: |
|
DECLARE_CLASS( Action, INextBotEventResponder ); |
|
|
|
Action( void ); |
|
virtual ~Action(); |
|
|
|
virtual const char *GetName( void ) const = 0; // return name of this action |
|
virtual bool IsNamed( const char *name ) const; // return true if given name matches the name of this Action |
|
virtual const char *GetFullName( void ) const; // return a temporary string showing the full lineage of this one action |
|
Actor *GetActor( void ) const; // return the Actor performing this Action (valid just before OnStart() is invoked) |
|
|
|
//----------------------------------------------------------------------------------------- |
|
/** |
|
* Try to start the Action. Result is immediately processed, |
|
* which can cause an immediate transition, another OnStart(), etc. |
|
* An Action can count on each OnStart() being followed (eventually) with an OnEnd(). |
|
*/ |
|
virtual ActionResult< Actor > OnStart( Actor *me, Action< Actor > *priorAction ) { return Continue(); } |
|
|
|
/** |
|
* Do the work of the Action. It is possible for Update to not be |
|
* called between a given OnStart/OnEnd pair due to immediate transitions. |
|
*/ |
|
virtual ActionResult< Actor > Update( Actor *me, float interval ) { return Continue(); } |
|
|
|
// Invoked when an Action is ended for any reason |
|
virtual void OnEnd( Actor *me, Action< Actor > *nextAction ) { } |
|
|
|
/* |
|
* When an Action is suspended by a new action. |
|
* Note that only CONTINUE and DONE are valid results. All other results will |
|
* be considered as a CONTINUE. |
|
*/ |
|
virtual ActionResult< Actor > OnSuspend( Actor *me, Action< Actor > *interruptingAction ) { return Continue(); } |
|
|
|
// When an Action is resumed after being suspended |
|
virtual ActionResult< Actor > OnResume( Actor *me, Action< Actor > *interruptingAction ) { return Continue(); } |
|
|
|
/** |
|
* To cause a state change, use these methods to create an ActionResult to |
|
* return from OnStart, Update, or OnResume. |
|
*/ |
|
ActionResult< Actor > Continue( void ) const; |
|
ActionResult< Actor > ChangeTo( Action< Actor > *action, const char *reason = NULL ) const; |
|
ActionResult< Actor > SuspendFor( Action< Actor > *action, const char *reason = NULL ) const; |
|
ActionResult< Actor > Done( const char *reason = NULL ) const; |
|
|
|
// create and return an Action to start as sub-action within this Action when it starts |
|
virtual Action< Actor > *InitialContainedAction( Actor *me ) { return NULL; } |
|
|
|
//----------------------------------------------------------------------------------------- |
|
/** |
|
* Override the event handler methods below to respond to events that occur during this Action |
|
* NOTE: These are identical to the events in INextBotEventResponder with the addition |
|
* of an actor argument and a return result. Their translators are located in the private area |
|
* below. |
|
*/ |
|
virtual EventDesiredResult< Actor > OnLeaveGround( Actor *me, CBaseEntity *ground ) { return TryContinue(); } |
|
virtual EventDesiredResult< Actor > OnLandOnGround( Actor *me, CBaseEntity *ground ) { return TryContinue(); } |
|
virtual EventDesiredResult< Actor > OnContact( Actor *me, CBaseEntity *other, CGameTrace *result = NULL ) { return TryContinue(); } |
|
virtual EventDesiredResult< Actor > OnMoveToSuccess( Actor *me, const Path *path ) { return TryContinue(); } |
|
virtual EventDesiredResult< Actor > OnMoveToFailure( Actor *me, const Path *path, MoveToFailureType reason ) { return TryContinue(); } |
|
virtual EventDesiredResult< Actor > OnStuck( Actor *me ) { return TryContinue(); } |
|
virtual EventDesiredResult< Actor > OnUnStuck( Actor *me ) { return TryContinue(); } |
|
virtual EventDesiredResult< Actor > OnPostureChanged( Actor *me ) { return TryContinue(); } |
|
virtual EventDesiredResult< Actor > OnAnimationActivityComplete( Actor *me, int activity ) { return TryContinue(); } |
|
virtual EventDesiredResult< Actor > OnAnimationActivityInterrupted( Actor *me, int activity ) { return TryContinue(); } |
|
virtual EventDesiredResult< Actor > OnAnimationEvent( Actor *me, animevent_t *event ) { return TryContinue(); } |
|
virtual EventDesiredResult< Actor > OnIgnite( Actor *me ) { return TryContinue(); } |
|
virtual EventDesiredResult< Actor > OnInjured( Actor *me, const CTakeDamageInfo &info ) { return TryContinue(); } |
|
virtual EventDesiredResult< Actor > OnKilled( Actor *me, const CTakeDamageInfo &info ) { return TryContinue(); } |
|
virtual EventDesiredResult< Actor > OnOtherKilled( Actor *me, CBaseCombatCharacter *victim, const CTakeDamageInfo &info ) { return TryContinue(); } |
|
virtual EventDesiredResult< Actor > OnSight( Actor *me, CBaseEntity *subject ) { return TryContinue(); } |
|
virtual EventDesiredResult< Actor > OnLostSight( Actor *me, CBaseEntity *subject ) { return TryContinue(); } |
|
virtual EventDesiredResult< Actor > OnSound( Actor *me, CBaseEntity *source, const Vector &pos, KeyValues *keys ) { return TryContinue(); } |
|
virtual EventDesiredResult< Actor > OnSpokeConcept( Actor *me, CBaseCombatCharacter *who, AIConcept_t concept, AI_Response *response ) { return TryContinue(); } |
|
virtual EventDesiredResult< Actor > OnWeaponFired( Actor *me, CBaseCombatCharacter *whoFired, CBaseCombatWeapon *weapon ) { return TryContinue(); } |
|
virtual EventDesiredResult< Actor > OnNavAreaChanged( Actor *me, CNavArea *newArea, CNavArea *oldArea ) { return TryContinue(); } |
|
virtual EventDesiredResult< Actor > OnModelChanged( Actor *me ) { return TryContinue(); } |
|
virtual EventDesiredResult< Actor > OnPickUp( Actor *me, CBaseEntity *item, CBaseCombatCharacter *giver ) { return TryContinue(); } |
|
virtual EventDesiredResult< Actor > OnDrop( Actor *me, CBaseEntity *item ) { return TryContinue(); } |
|
virtual EventDesiredResult< Actor > OnActorEmoted( Actor *me, CBaseCombatCharacter *emoter, int emote ) { return TryContinue(); } |
|
|
|
virtual EventDesiredResult< Actor > OnCommandAttack( Actor *me, CBaseEntity *victim ) { return TryContinue(); } |
|
virtual EventDesiredResult< Actor > OnCommandApproach( Actor *me, const Vector &pos, float range ) { return TryContinue(); } |
|
virtual EventDesiredResult< Actor > OnCommandApproach( Actor *me, CBaseEntity *goal ) { return TryContinue(); } |
|
virtual EventDesiredResult< Actor > OnCommandRetreat( Actor *me, CBaseEntity *threat, float range ) { return TryContinue(); } |
|
virtual EventDesiredResult< Actor > OnCommandPause( Actor *me, float duration ) { return TryContinue(); } |
|
virtual EventDesiredResult< Actor > OnCommandResume( Actor *me ) { return TryContinue(); } |
|
virtual EventDesiredResult< Actor > OnCommandString( Actor *me, const char *command ) { return TryContinue(); } |
|
|
|
virtual EventDesiredResult< Actor > OnShoved( Actor *me, CBaseEntity *pusher ) { return TryContinue(); } |
|
virtual EventDesiredResult< Actor > OnBlinded( Actor *me, CBaseEntity *blinder ) { return TryContinue(); } |
|
virtual EventDesiredResult< Actor > OnTerritoryContested( Actor *me, int territoryID ) { return TryContinue(); } |
|
virtual EventDesiredResult< Actor > OnTerritoryCaptured( Actor *me, int territoryID ) { return TryContinue(); } |
|
virtual EventDesiredResult< Actor > OnTerritoryLost( Actor *me, int territoryID ) { return TryContinue(); } |
|
virtual EventDesiredResult< Actor > OnWin( Actor *me ) { return TryContinue(); } |
|
virtual EventDesiredResult< Actor > OnLose( Actor *me ) { return TryContinue(); } |
|
|
|
#ifdef DOTA_SERVER_DLL |
|
virtual EventDesiredResult< Actor > OnCommandMoveTo( Actor *me, const Vector &pos ) { return TryContinue(); } |
|
virtual EventDesiredResult< Actor > OnCommandMoveToAggressive( Actor *me, const Vector &pos ) { return TryContinue(); } |
|
virtual EventDesiredResult< Actor > OnCommandAttack( Actor *me, CBaseEntity *victim, bool bDeny ) { return TryContinue(); } |
|
virtual EventDesiredResult< Actor > OnCastAbilityNoTarget( Actor *me, CDOTABaseAbility *ability ) { return TryContinue(); } |
|
virtual EventDesiredResult< Actor > OnCastAbilityOnPosition( Actor *me, CDOTABaseAbility *ability, const Vector &pos ) { return TryContinue(); } |
|
virtual EventDesiredResult< Actor > OnCastAbilityOnTarget( Actor *me, CDOTABaseAbility *ability, CBaseEntity *target ) { return TryContinue(); } |
|
virtual EventDesiredResult< Actor > OnDropItem( Actor *me, const Vector &pos, CBaseEntity *item ) { return TryContinue(); } |
|
virtual EventDesiredResult< Actor > OnPickupItem( Actor *me, CBaseEntity *item ) { return TryContinue(); } |
|
virtual EventDesiredResult< Actor > OnPickupRune( Actor *me, CBaseEntity *item ) { return TryContinue(); } |
|
virtual EventDesiredResult< Actor > OnStop( Actor *me ) { return TryContinue(); } |
|
virtual EventDesiredResult< Actor > OnFriendThreatened( Actor *me, CBaseEntity *friendly, CBaseEntity *threat ) { return TryContinue(); } |
|
virtual EventDesiredResult< Actor > OnCancelAttack( Actor *me, CBaseEntity *pTarget ) { return TryContinue(); } |
|
virtual EventDesiredResult< Actor > OnDominated( Actor *me ) { return TryContinue(); } |
|
virtual EventDesiredResult< Actor > OnWarped( Actor *me, Vector vStartPos ) { return TryContinue(); } |
|
#endif |
|
|
|
/** |
|
* Event handlers must return one of these. |
|
*/ |
|
EventDesiredResult< Actor > TryContinue( EventResultPriorityType priority = RESULT_TRY ) const; |
|
EventDesiredResult< Actor > TryChangeTo( Action< Actor > *action, EventResultPriorityType priority = RESULT_TRY, const char *reason = NULL ) const; |
|
EventDesiredResult< Actor > TrySuspendFor( Action< Actor > *action, EventResultPriorityType priority = RESULT_TRY, const char *reason = NULL ) const; |
|
EventDesiredResult< Actor > TryDone( EventResultPriorityType priority = RESULT_TRY, const char *reason = NULL ) const; |
|
EventDesiredResult< Actor > TryToSustain( EventResultPriorityType priority = RESULT_TRY, const char *reason = NULL ) const; |
|
|
|
|
|
//----------------------------------------------------------------------------------------- |
|
Action< Actor > *GetActiveChildAction( void ) const; |
|
Action< Actor > *GetParentAction( void ) const; // the Action that I'm running inside of |
|
|
|
bool IsSuspended( void ) const; // return true if we are currently suspended for another Action |
|
|
|
const char *DebugString( void ) const; // return a temporary string describing the current action stack for debugging |
|
|
|
/** |
|
* Sometimes we want to pass through other NextBots. OnContact() will always |
|
* be invoked, but collision resolution can be skipped if this |
|
* method returns false. |
|
*/ |
|
virtual bool IsAbleToBlockMovementOf( const INextBot *botInMotion ) const { return true; } |
|
|
|
// INextBotEventResponder propagation ---------------------------------------------------------------------- |
|
virtual INextBotEventResponder *FirstContainedResponder( void ) const; |
|
virtual INextBotEventResponder *NextContainedResponder( INextBotEventResponder *current ) const; |
|
|
|
|
|
private: |
|
|
|
/** |
|
* These macros are used below to translate INextBotEventResponder event methods |
|
* into Action event handler methods |
|
*/ |
|
#define PROCESS_EVENT( METHOD ) \ |
|
{ \ |
|
if ( !m_isStarted ) \ |
|
return; \ |
|
\ |
|
Action< Actor > *_action = this; \ |
|
EventDesiredResult< Actor > _result; \ |
|
\ |
|
while( _action ) \ |
|
{ \ |
|
if ( m_actor && (m_actor->IsDebugging(NEXTBOT_EVENTS) || NextBotDebugHistory.GetBool())) \ |
|
{ \ |
|
m_actor->DebugConColorMsg( NEXTBOT_EVENTS, Color( 100, 100, 100, 255 ), "%3.2f: %s:%s: %s received EVENT %s\n", gpGlobals->curtime, m_actor->GetDebugIdentifier(), m_behavior->GetName(), _action->GetFullName(), #METHOD ); \ |
|
} \ |
|
_result = _action->METHOD( m_actor ); \ |
|
if ( !_result.IsContinue() ) \ |
|
break; \ |
|
_action = _action->GetActionBuriedUnderMe(); \ |
|
} \ |
|
\ |
|
if ( _action ) \ |
|
{ \ |
|
if ( m_actor && _result.IsRequestingChange() && (m_actor->IsDebugging(NEXTBOT_BEHAVIOR) || NextBotDebugHistory.GetBool()) ) \ |
|
{ \ |
|
m_actor->DebugConColorMsg( NEXTBOT_BEHAVIOR, Color( 255, 255, 0, 255 ), "%3.2f: %s:%s: ", gpGlobals->curtime, m_actor->GetDebugIdentifier(), m_behavior->GetName() ); \ |
|
m_actor->DebugConColorMsg( NEXTBOT_BEHAVIOR, Color( 255, 255, 255, 255 ), "%s ", _action->GetFullName() ); \ |
|
m_actor->DebugConColorMsg( NEXTBOT_BEHAVIOR, Color( 255, 255, 0, 255 ), "reponded to EVENT %s with ", #METHOD ); \ |
|
m_actor->DebugConColorMsg( NEXTBOT_BEHAVIOR, Color( 255, 0, 0, 255 ), "%s %s ", _result.GetTypeName(), _result.m_action ? _result.m_action->GetName() : "" ); \ |
|
m_actor->DebugConColorMsg( NEXTBOT_BEHAVIOR, Color( 0, 255, 0, 255 ), "%s\n", _result.m_reason ? _result.m_reason : "" ); \ |
|
} \ |
|
\ |
|
_action->StorePendingEventResult( _result, #METHOD ); \ |
|
} \ |
|
\ |
|
INextBotEventResponder::METHOD(); \ |
|
} |
|
|
|
|
|
#define PROCESS_EVENT_WITH_1_ARG( METHOD, ARG1 ) \ |
|
{ \ |
|
if ( !m_isStarted ) \ |
|
return; \ |
|
\ |
|
Action< Actor > *_action = this; \ |
|
EventDesiredResult< Actor > _result; \ |
|
\ |
|
while( _action ) \ |
|
{ \ |
|
if ( m_actor && (m_actor->IsDebugging(NEXTBOT_EVENTS) || NextBotDebugHistory.GetBool()) ) \ |
|
{ \ |
|
m_actor->DebugConColorMsg( NEXTBOT_EVENTS, Color( 100, 100, 100, 255 ), "%3.2f: %s:%s: %s received EVENT %s\n", gpGlobals->curtime, m_actor->GetDebugIdentifier(), m_behavior->GetName(), _action->GetFullName(), #METHOD ); \ |
|
} \ |
|
_result = _action->METHOD( m_actor, ARG1 ); \ |
|
if ( !_result.IsContinue() ) \ |
|
break; \ |
|
_action = _action->GetActionBuriedUnderMe(); \ |
|
} \ |
|
\ |
|
if ( _action ) \ |
|
{ \ |
|
if ( m_actor && (m_actor->IsDebugging(NEXTBOT_BEHAVIOR) || NextBotDebugHistory.GetBool()) && _result.IsRequestingChange() && _action ) \ |
|
{ \ |
|
m_actor->DebugConColorMsg( NEXTBOT_BEHAVIOR, Color( 255, 255, 0, 255 ), "%3.2f: %s:%s: ", gpGlobals->curtime, m_actor->GetDebugIdentifier(), m_behavior->GetName() ); \ |
|
m_actor->DebugConColorMsg( NEXTBOT_BEHAVIOR, Color( 255, 255, 255, 255 ), "%s ", _action->GetFullName() ); \ |
|
m_actor->DebugConColorMsg( NEXTBOT_BEHAVIOR, Color( 255, 255, 0, 255 ), "reponded to EVENT %s with ", #METHOD ); \ |
|
m_actor->DebugConColorMsg( NEXTBOT_BEHAVIOR, Color( 255, 0, 0, 255 ), "%s %s ", _result.GetTypeName(), _result.m_action ? _result.m_action->GetName() : "" ); \ |
|
m_actor->DebugConColorMsg( NEXTBOT_BEHAVIOR, Color( 0, 255, 0, 255 ), "%s\n", _result.m_reason ? _result.m_reason : "" ); \ |
|
} \ |
|
\ |
|
_action->StorePendingEventResult( _result, #METHOD ); \ |
|
} \ |
|
\ |
|
INextBotEventResponder::METHOD( ARG1 ); \ |
|
} |
|
|
|
|
|
#define PROCESS_EVENT_WITH_2_ARGS( METHOD, ARG1, ARG2 ) \ |
|
{ \ |
|
if ( !m_isStarted ) \ |
|
return; \ |
|
\ |
|
Action< Actor > *_action = this; \ |
|
EventDesiredResult< Actor > _result; \ |
|
\ |
|
while( _action ) \ |
|
{ \ |
|
if ( m_actor && (m_actor->IsDebugging(NEXTBOT_EVENTS) || NextBotDebugHistory.GetBool()) ) \ |
|
{ \ |
|
m_actor->DebugConColorMsg( NEXTBOT_EVENTS, Color( 100, 100, 100, 255 ), "%3.2f: %s:%s: %s received EVENT %s\n", gpGlobals->curtime, m_actor->GetDebugIdentifier(), m_behavior->GetName(), _action->GetFullName(), #METHOD ); \ |
|
} \ |
|
_result = _action->METHOD( m_actor, ARG1, ARG2 ); \ |
|
if ( !_result.IsContinue() ) \ |
|
break; \ |
|
_action = _action->GetActionBuriedUnderMe(); \ |
|
} \ |
|
\ |
|
if ( _action ) \ |
|
{ \ |
|
if ( m_actor && (m_actor->IsDebugging(NEXTBOT_BEHAVIOR) || NextBotDebugHistory.GetBool()) && _result.IsRequestingChange() && _action ) \ |
|
{ \ |
|
m_actor->DebugConColorMsg( NEXTBOT_BEHAVIOR, Color( 255, 255, 0, 255 ), "%3.2f: %s:%s: ", gpGlobals->curtime, m_actor->GetDebugIdentifier(), m_behavior->GetName() ); \ |
|
m_actor->DebugConColorMsg( NEXTBOT_BEHAVIOR, Color( 255, 255, 255, 255 ), "%s ", _action->GetFullName() ); \ |
|
m_actor->DebugConColorMsg( NEXTBOT_BEHAVIOR, Color( 255, 255, 0, 255 ), "reponded to EVENT %s with ", #METHOD ); \ |
|
m_actor->DebugConColorMsg( NEXTBOT_BEHAVIOR, Color( 255, 0, 0, 255 ), "%s %s ", _result.GetTypeName(), _result.m_action ? _result.m_action->GetName() : "" ); \ |
|
m_actor->DebugConColorMsg( NEXTBOT_BEHAVIOR, Color( 0, 255, 0, 255 ), "%s\n", _result.m_reason ? _result.m_reason : "" ); \ |
|
} \ |
|
\ |
|
_action->StorePendingEventResult( _result, #METHOD ); \ |
|
} \ |
|
\ |
|
INextBotEventResponder::METHOD( ARG1, ARG2 ); \ |
|
} |
|
|
|
|
|
#define PROCESS_EVENT_WITH_3_ARGS( METHOD, ARG1, ARG2, ARG3 ) \ |
|
{ \ |
|
if ( !m_isStarted ) \ |
|
return; \ |
|
\ |
|
Action< Actor > *_action = this; \ |
|
EventDesiredResult< Actor > _result; \ |
|
\ |
|
while( _action ) \ |
|
{ \ |
|
if ( m_actor && (m_actor->IsDebugging(NEXTBOT_EVENTS) || NextBotDebugHistory.GetBool()) ) \ |
|
{ \ |
|
m_actor->DebugConColorMsg( NEXTBOT_EVENTS, Color( 100, 100, 100, 255 ), "%3.2f: %s:%s: %s received EVENT %s\n", gpGlobals->curtime, m_actor->GetDebugIdentifier(), m_behavior->GetName(), _action->GetFullName(), #METHOD ); \ |
|
} \ |
|
_result = _action->METHOD( m_actor, ARG1, ARG2, ARG3 ); \ |
|
if ( !_result.IsContinue() ) \ |
|
break; \ |
|
_action = _action->GetActionBuriedUnderMe(); \ |
|
} \ |
|
\ |
|
if ( _action ) \ |
|
{ \ |
|
if ( m_actor && (m_actor->IsDebugging(NEXTBOT_BEHAVIOR) || NextBotDebugHistory.GetBool()) && _result.IsRequestingChange() && _action ) \ |
|
{ \ |
|
m_actor->DebugConColorMsg( NEXTBOT_BEHAVIOR, Color( 255, 255, 0, 255 ), "%3.2f: %s:%s: ", gpGlobals->curtime, m_actor->GetDebugIdentifier(), m_behavior->GetName() ); \ |
|
m_actor->DebugConColorMsg( NEXTBOT_BEHAVIOR, Color( 255, 255, 255, 255 ), "%s ", _action->GetFullName() ); \ |
|
m_actor->DebugConColorMsg( NEXTBOT_BEHAVIOR, Color( 255, 255, 0, 255 ), "reponded to EVENT %s with ", #METHOD ); \ |
|
m_actor->DebugConColorMsg( NEXTBOT_BEHAVIOR, Color( 255, 0, 0, 255 ), "%s %s ", _result.GetTypeName(), _result.m_action ? _result.m_action->GetName() : "" ); \ |
|
m_actor->DebugConColorMsg( NEXTBOT_BEHAVIOR, Color( 0, 255, 0, 255 ), "%s\n", _result.m_reason ? _result.m_reason : "" ); \ |
|
} \ |
|
\ |
|
_action->StorePendingEventResult( _result, #METHOD ); \ |
|
} \ |
|
\ |
|
INextBotEventResponder::METHOD( ARG1, ARG2, ARG3 ); \ |
|
} |
|
|
|
|
|
/** |
|
* Translate incoming events into Action events |
|
* DO NOT OVERRIDE THESE METHODS |
|
*/ |
|
virtual void OnLeaveGround( CBaseEntity *ground ) { PROCESS_EVENT_WITH_1_ARG( OnLeaveGround, ground ); } |
|
virtual void OnLandOnGround( CBaseEntity *ground ) { PROCESS_EVENT_WITH_1_ARG( OnLandOnGround, ground ); } |
|
virtual void OnContact( CBaseEntity *other, CGameTrace *result ) { PROCESS_EVENT_WITH_2_ARGS( OnContact, other, result ); } |
|
virtual void OnMoveToSuccess( const Path *path ) { PROCESS_EVENT_WITH_1_ARG( OnMoveToSuccess, path ); } |
|
virtual void OnMoveToFailure( const Path *path, MoveToFailureType reason ) { PROCESS_EVENT_WITH_2_ARGS( OnMoveToFailure, path, reason ); } |
|
virtual void OnStuck( void ) { PROCESS_EVENT( OnStuck ); } |
|
virtual void OnUnStuck( void ) { PROCESS_EVENT( OnUnStuck ); } |
|
virtual void OnPostureChanged( void ) { PROCESS_EVENT( OnPostureChanged ); } |
|
virtual void OnAnimationActivityComplete( int activity ) { PROCESS_EVENT_WITH_1_ARG( OnAnimationActivityComplete, activity ); } |
|
virtual void OnAnimationActivityInterrupted( int activity ) { PROCESS_EVENT_WITH_1_ARG( OnAnimationActivityInterrupted, activity ); } |
|
virtual void OnAnimationEvent( animevent_t *event ) { PROCESS_EVENT_WITH_1_ARG( OnAnimationEvent, event ); } |
|
virtual void OnIgnite( void ) { PROCESS_EVENT( OnIgnite ); } |
|
virtual void OnInjured( const CTakeDamageInfo &info ) { PROCESS_EVENT_WITH_1_ARG( OnInjured, info ); } |
|
virtual void OnKilled( const CTakeDamageInfo &info ) { PROCESS_EVENT_WITH_1_ARG( OnKilled, info ); } |
|
virtual void OnOtherKilled( CBaseCombatCharacter *victim, const CTakeDamageInfo &info ) { PROCESS_EVENT_WITH_2_ARGS( OnOtherKilled, victim, info ); } |
|
virtual void OnSight( CBaseEntity *subject ) { PROCESS_EVENT_WITH_1_ARG( OnSight, subject ); } |
|
virtual void OnLostSight( CBaseEntity *subject ) { PROCESS_EVENT_WITH_1_ARG( OnLostSight, subject ); } |
|
virtual void OnSound( CBaseEntity *source, const Vector &pos, KeyValues *keys ) { PROCESS_EVENT_WITH_3_ARGS( OnSound, source, pos, keys ); } |
|
virtual void OnSpokeConcept( CBaseCombatCharacter *who, AIConcept_t concept, AI_Response *response ) { PROCESS_EVENT_WITH_3_ARGS( OnSpokeConcept, who, concept, response ); } |
|
virtual void OnWeaponFired( CBaseCombatCharacter *whoFired, CBaseCombatWeapon *weapon ) { PROCESS_EVENT_WITH_2_ARGS( OnWeaponFired, whoFired, weapon ); } |
|
virtual void OnNavAreaChanged( CNavArea *newArea, CNavArea *oldArea ) { PROCESS_EVENT_WITH_2_ARGS( OnNavAreaChanged, newArea, oldArea ); } |
|
virtual void OnModelChanged( void ) { PROCESS_EVENT( OnModelChanged ); } |
|
virtual void OnPickUp( CBaseEntity *item, CBaseCombatCharacter *giver ) { PROCESS_EVENT_WITH_2_ARGS( OnPickUp, item, giver ); } |
|
virtual void OnDrop( CBaseEntity *item ) { PROCESS_EVENT_WITH_1_ARG( OnDrop, item ); } |
|
virtual void OnActorEmoted( CBaseCombatCharacter *emoter, int emote ) { PROCESS_EVENT_WITH_2_ARGS( OnActorEmoted, emoter, emote ); } |
|
|
|
virtual void OnCommandAttack( CBaseEntity *victim ) { PROCESS_EVENT_WITH_1_ARG( OnCommandAttack, victim ); } |
|
virtual void OnCommandApproach( const Vector &pos, float range ) { PROCESS_EVENT_WITH_2_ARGS( OnCommandApproach, pos, range ); } |
|
virtual void OnCommandApproach( CBaseEntity *goal ) { PROCESS_EVENT_WITH_1_ARG( OnCommandApproach, goal ); } |
|
virtual void OnCommandRetreat( CBaseEntity *threat, float range ) { PROCESS_EVENT_WITH_2_ARGS( OnCommandRetreat, threat, range ); } |
|
virtual void OnCommandPause( float duration ) { PROCESS_EVENT_WITH_1_ARG( OnCommandPause, duration ); } |
|
virtual void OnCommandResume( void ) { PROCESS_EVENT( OnCommandResume ); } |
|
virtual void OnCommandString( const char *command ) { PROCESS_EVENT_WITH_1_ARG( OnCommandString, command ); } |
|
|
|
virtual void OnShoved( CBaseEntity *pusher ) { PROCESS_EVENT_WITH_1_ARG( OnShoved, pusher ); } |
|
virtual void OnBlinded( CBaseEntity *blinder ) { PROCESS_EVENT_WITH_1_ARG( OnBlinded, blinder ); } |
|
virtual void OnTerritoryContested( int territoryID ) { PROCESS_EVENT_WITH_1_ARG( OnTerritoryContested, territoryID ); } |
|
virtual void OnTerritoryCaptured( int territoryID ) { PROCESS_EVENT_WITH_1_ARG( OnTerritoryCaptured, territoryID ); } |
|
virtual void OnTerritoryLost( int territoryID ) { PROCESS_EVENT_WITH_1_ARG( OnTerritoryLost, territoryID ); } |
|
virtual void OnWin( void ) { PROCESS_EVENT( OnWin ); } |
|
virtual void OnLose( void ) { PROCESS_EVENT( OnLose ); } |
|
|
|
#ifdef DOTA_SERVER_DLL |
|
virtual void OnCommandMoveTo( const Vector &pos ) { PROCESS_EVENT_WITH_1_ARG( OnCommandMoveTo, pos ); } |
|
virtual void OnCommandMoveToAggressive( const Vector &pos ) { PROCESS_EVENT_WITH_1_ARG( OnCommandMoveToAggressive, pos ); } |
|
virtual void OnCommandAttack( CBaseEntity *victim, bool bDeny ) { PROCESS_EVENT_WITH_2_ARGS( OnCommandAttack, victim, bDeny ); } |
|
virtual void OnCastAbilityNoTarget( CDOTABaseAbility *ability ) { PROCESS_EVENT_WITH_1_ARG( OnCastAbilityNoTarget, ability ); } |
|
virtual void OnCastAbilityOnPosition( CDOTABaseAbility *ability, const Vector &pos ) { PROCESS_EVENT_WITH_2_ARGS( OnCastAbilityOnPosition, ability, pos ); } |
|
virtual void OnCastAbilityOnTarget( CDOTABaseAbility *ability, CBaseEntity *target ) { PROCESS_EVENT_WITH_2_ARGS( OnCastAbilityOnTarget, ability, target ); } |
|
virtual void OnDropItem( const Vector &pos, CBaseEntity *item ) { PROCESS_EVENT_WITH_2_ARGS( OnDropItem, pos, item ); } |
|
virtual void OnPickupItem( CBaseEntity *item ) { PROCESS_EVENT_WITH_1_ARG( OnPickupItem, item ); } |
|
virtual void OnPickupRune( CBaseEntity *item ) { PROCESS_EVENT_WITH_1_ARG( OnPickupRune, item ); } |
|
virtual void OnStop() { PROCESS_EVENT( OnStop ); } |
|
virtual void OnFriendThreatened( CBaseEntity *friendly, CBaseEntity *threat ) { PROCESS_EVENT_WITH_2_ARGS( OnFriendThreatened, friendly, threat ); } |
|
virtual void OnCancelAttack( CBaseEntity *pTarget ) { PROCESS_EVENT_WITH_1_ARG( OnCancelAttack, pTarget ); } |
|
virtual void OnDominated() { PROCESS_EVENT( OnDominated ); } |
|
virtual void OnWarped( Vector vStartPos ) { PROCESS_EVENT_WITH_1_ARG( OnWarped, vStartPos ); } |
|
#endif |
|
|
|
friend class Behavior< Actor>; // the containing Behavior class |
|
Behavior< Actor > *m_behavior; // the Behavior this Action is part of |
|
|
|
Action< Actor > *m_parent; // the Action that contains us |
|
Action< Actor > *m_child; // the ACTIVE Action we contain, top of the stack. Use m_buriedUnderMe, m_coveringMe on the child to traverse to other suspended children |
|
|
|
Action< Actor > *m_buriedUnderMe; // the Action just "under" us in the stack that we will resume to when we finish |
|
Action< Actor > *m_coveringMe; // the Action just "above" us in the stack that will resume to us when it finishes |
|
|
|
Actor *m_actor; // only valid after OnStart() |
|
mutable EventDesiredResult< Actor > m_eventResult; // set by event handlers |
|
bool m_isStarted; // Action doesn't start until OnStart() is invoked |
|
bool m_isSuspended; // are we suspended for another Action |
|
|
|
Action< Actor > *GetActionBuriedUnderMe( void ) const // return Action just "under" us that we will resume to when we finish |
|
{ |
|
return m_buriedUnderMe; |
|
} |
|
|
|
Action< Actor > *GetActionCoveringMe( void ) const // return Action just "above" us that will resume to us when it finishes |
|
{ |
|
return m_coveringMe; |
|
} |
|
|
|
/** |
|
* If any Action buried underneath me has either exited |
|
* or is changing to a different Action, we're "out of scope" |
|
*/ |
|
bool IsOutOfScope( void ) const |
|
{ |
|
for( Action< Actor > *under = GetActionBuriedUnderMe(); under; under = under->GetActionBuriedUnderMe() ) |
|
{ |
|
if ( under->m_eventResult.m_type == CHANGE_TO || |
|
under->m_eventResult.m_type == DONE ) |
|
{ |
|
return true; |
|
} |
|
} |
|
return false; |
|
} |
|
|
|
/** |
|
* Process any pending events with the stack. This is called |
|
* by the active Action on the top of the stack, and walks |
|
* through any buried Actions checking for pending event results. |
|
*/ |
|
ActionResult< Actor > ProcessPendingEvents( void ) const |
|
{ |
|
// if an event has requested a change, honor it |
|
if ( m_eventResult.IsRequestingChange() ) |
|
{ |
|
ActionResult< Actor > result( m_eventResult.m_type, m_eventResult.m_action, m_eventResult.m_reason ); |
|
|
|
// clear event result in case this change is a suspend and we later resume this action |
|
m_eventResult = TryContinue( RESULT_NONE ); |
|
|
|
return result; |
|
} |
|
|
|
// check for pending event changes buried in the stack |
|
Action< Actor > *under = GetActionBuriedUnderMe(); |
|
while( under ) |
|
{ |
|
if ( under->m_eventResult.m_type == SUSPEND_FOR ) |
|
{ |
|
// process this pending event in-place and push new Action on the top of the stack |
|
ActionResult< Actor > result( under->m_eventResult.m_type, under->m_eventResult.m_action, under->m_eventResult.m_reason ); |
|
|
|
// clear event result in case this change is a suspend and we later resume this action |
|
under->m_eventResult = TryContinue( RESULT_NONE ); |
|
|
|
return result; |
|
} |
|
|
|
under = under->GetActionBuriedUnderMe(); |
|
} |
|
|
|
return Continue(); |
|
} |
|
|
|
// given the result of this Action's work, apply the result to potentially cause a state transition |
|
Action< Actor > * ApplyResult( Actor *me, Behavior< Actor > *behavior, ActionResult< Actor > result ); |
|
|
|
/** |
|
* The methods below do the bookkeeping of each event, propagate the activity through the hierarchy, |
|
* and invoke the virtual event for each. |
|
*/ |
|
ActionResult< Actor > InvokeOnStart( Actor *me, Behavior< Actor > *behavior, Action< Actor > *priorAction, Action< Actor > *buriedUnderMeAction ); |
|
ActionResult< Actor > InvokeUpdate( Actor *me, Behavior< Actor > *behavior, float interval ); |
|
void InvokeOnEnd( Actor *me, Behavior< Actor > *behavior, Action< Actor > *nextAction ); |
|
Action< Actor > * InvokeOnSuspend( Actor *me, Behavior< Actor > *behavior, Action< Actor > *interruptingAction ); |
|
ActionResult< Actor > InvokeOnResume( Actor *me, Behavior< Actor > *behavior, Action< Actor > *interruptingAction ); |
|
|
|
/** |
|
* Store the given event result, attending to priorities |
|
*/ |
|
void StorePendingEventResult( const EventDesiredResult< Actor > &result, const char *eventName ) |
|
{ |
|
if ( result.IsContinue() ) |
|
{ |
|
return; |
|
} |
|
|
|
if ( result.m_priority >= m_eventResult.m_priority ) |
|
{ |
|
if ( m_eventResult.m_priority == RESULT_CRITICAL ) |
|
{ |
|
if ( developer.GetBool() ) |
|
{ |
|
DevMsg( "%3.2f: WARNING: %s::%s() RESULT_CRITICAL collision\n", gpGlobals->curtime, GetName(), eventName ); |
|
} |
|
} |
|
|
|
// new result as important or more so - destroy the replaced action |
|
if ( m_eventResult.m_action ) |
|
{ |
|
delete m_eventResult.m_action; |
|
} |
|
|
|
// We keep the most recently processed event because this allows code to check history/state to |
|
// do custom event collision handling. If we keep the first event at this priority and discard |
|
// subsequent events (original behavior) there is no way to predict future collision resolutions (MSB). |
|
m_eventResult = result; |
|
} |
|
else |
|
{ |
|
// new result is lower priority than previously stored result - discard it |
|
if ( result.m_action ) |
|
{ |
|
// destroy the unused action |
|
delete result.m_action; |
|
} |
|
} |
|
} |
|
|
|
char *BuildDecoratedName( char *name, const Action< Actor > *action ) const; // recursive name outMsg for DebugString() |
|
|
|
void PrintStateToConsole( void ) const; |
|
}; |
|
|
|
|
|
//------------------------------------------------------------------------------------------- |
|
template < typename Actor > |
|
Action< Actor >::Action( void ) |
|
{ |
|
m_parent = NULL; |
|
m_child = NULL; |
|
m_buriedUnderMe = NULL; |
|
m_coveringMe = NULL; |
|
m_actor = NULL; |
|
m_behavior = NULL; |
|
|
|
m_isStarted = false; |
|
m_isSuspended = false; |
|
|
|
m_eventResult = TryContinue( RESULT_NONE ); |
|
|
|
#ifdef DEBUG_BEHAVIOR_MEMORY |
|
ConColorMsg( Color( 255, 0, 255, 255 ), "%3.2f: NEW %0X\n", gpGlobals->curtime, this ); |
|
#endif |
|
} |
|
|
|
|
|
//------------------------------------------------------------------------------------------- |
|
template < typename Actor > |
|
Action< Actor >::~Action() |
|
{ |
|
#ifdef DEBUG_BEHAVIOR_MEMORY |
|
ConColorMsg( Color( 255, 0, 255, 255 ), "%3.2f: DELETE %0X\n", gpGlobals->curtime, this ); |
|
#endif |
|
|
|
if ( m_parent ) |
|
{ |
|
// if I'm my parent's active child, update parent's pointer |
|
if ( m_parent->m_child == this ) |
|
{ |
|
m_parent->m_child = m_buriedUnderMe; |
|
} |
|
} |
|
|
|
// delete all my children. |
|
// our m_child pointer always points to the topmost |
|
// child in the stack, so work our way back thru the |
|
// 'buried' children and delete them. |
|
Action< Actor > *child, *next = NULL; |
|
for( child = m_child; child; child = next ) |
|
{ |
|
next = child->m_buriedUnderMe; |
|
delete child; |
|
} |
|
|
|
if ( m_buriedUnderMe ) |
|
{ |
|
// we're going away, so my buried sibling is now on top |
|
m_buriedUnderMe->m_coveringMe = NULL; |
|
} |
|
|
|
// delete any actions stacked on top of me |
|
if ( m_coveringMe ) |
|
{ |
|
// recursion will march down the chain |
|
delete m_coveringMe; |
|
} |
|
|
|
// delete any pending event result |
|
if ( m_eventResult.m_action ) |
|
{ |
|
delete m_eventResult.m_action; |
|
} |
|
} |
|
|
|
|
|
template < typename Actor > |
|
bool Action< Actor >::IsNamed( const char *name ) const |
|
{ |
|
return FStrEq( GetName(), name ); |
|
} |
|
|
|
|
|
template < typename Actor > |
|
Actor *Action< Actor >::GetActor( void ) const |
|
{ |
|
return m_actor; |
|
} |
|
|
|
template < typename Actor > |
|
ActionResult< Actor > Action< Actor >::Continue( void ) const |
|
{ |
|
return ActionResult< Actor >( CONTINUE, NULL, NULL ); |
|
} |
|
|
|
template < typename Actor > |
|
ActionResult< Actor > Action< Actor >::ChangeTo( Action< Actor > *action, const char *reason ) const |
|
{ |
|
return ActionResult< Actor >( CHANGE_TO, action, reason ); |
|
} |
|
|
|
template < typename Actor > |
|
ActionResult< Actor > Action< Actor >::SuspendFor( Action< Actor > *action, const char *reason ) const |
|
{ |
|
// clear any pending transitions requested by events, or this SuspendFor will |
|
// immediately be out of scope |
|
m_eventResult = TryContinue( RESULT_NONE ); |
|
|
|
return ActionResult< Actor >( SUSPEND_FOR, action, reason ); |
|
} |
|
|
|
template < typename Actor > |
|
ActionResult< Actor > Action< Actor >::Done( const char *reason ) const |
|
{ |
|
return ActionResult< Actor >( DONE, NULL, reason ); |
|
} |
|
|
|
|
|
//------------------------------------------------------------------------------------------- |
|
template < typename Actor > |
|
EventDesiredResult< Actor > Action< Actor >::TryContinue( EventResultPriorityType priority ) const |
|
{ |
|
return EventDesiredResult< Actor >( CONTINUE, NULL, priority ); |
|
} |
|
|
|
template < typename Actor > |
|
EventDesiredResult< Actor > Action< Actor >::TryChangeTo( Action< Actor > *action, EventResultPriorityType priority, const char *reason ) const |
|
{ |
|
return EventDesiredResult< Actor >( CHANGE_TO, action, priority, reason ); |
|
} |
|
|
|
template < typename Actor > |
|
EventDesiredResult< Actor > Action< Actor >::TrySuspendFor( Action< Actor > *action, EventResultPriorityType priority, const char *reason ) const |
|
{ |
|
return EventDesiredResult< Actor >( SUSPEND_FOR, action, priority, reason ); |
|
} |
|
|
|
template < typename Actor > |
|
EventDesiredResult< Actor > Action< Actor >::TryDone( EventResultPriorityType priority, const char *reason /*= NULL*/ ) const |
|
{ |
|
return EventDesiredResult< Actor >( DONE, NULL, priority, reason ); |
|
} |
|
|
|
template < typename Actor > |
|
EventDesiredResult< Actor > Action< Actor >::TryToSustain( EventResultPriorityType priority, const char *reason /*= NULL*/ ) const |
|
{ |
|
return EventDesiredResult< Actor >( SUSTAIN, NULL, priority, reason ); |
|
} |
|
|
|
|
|
//------------------------------------------------------------------------------------------- |
|
template < typename Actor > |
|
Action< Actor > *Action< Actor >::GetActiveChildAction( void ) const |
|
{ |
|
return m_child; |
|
} |
|
|
|
|
|
//------------------------------------------------------------------------------------------- |
|
// the Action that I'm running inside of |
|
template < typename Actor > |
|
Action< Actor > *Action< Actor >::GetParentAction( void ) const |
|
{ |
|
return m_parent; |
|
} |
|
|
|
|
|
//------------------------------------------------------------------------------------------- |
|
/** |
|
* Return true if we are currently suspended for another Action |
|
*/ |
|
template < typename Actor > |
|
bool Action< Actor >::IsSuspended( void ) const |
|
{ |
|
return m_isSuspended; |
|
} |
|
|
|
|
|
//------------------------------------------------------------------------------------------- |
|
/** |
|
* Start this Action. |
|
* The act of calling InvokeOnStart is the edge case that 'enters' a state. |
|
*/ |
|
template < typename Actor > |
|
ActionResult< Actor > Action< Actor >::InvokeOnStart( Actor *me, Behavior< Actor > *behavior, Action< Actor > *priorAction, Action< Actor > *buriedUnderMeAction ) |
|
{ |
|
// debug display |
|
if ( (me->IsDebugging(NEXTBOT_BEHAVIOR) || NextBotDebugHistory.GetBool()) ) |
|
{ |
|
me->DebugConColorMsg( NEXTBOT_BEHAVIOR, Color( 255, 255, 150, 255 ), "%3.2f: %s:%s: ", gpGlobals->curtime, me->GetDebugIdentifier(), behavior->GetName() ); |
|
me->DebugConColorMsg( NEXTBOT_BEHAVIOR, Color( 0, 255, 0, 255 ), " STARTING " ); |
|
me->DebugConColorMsg( NEXTBOT_BEHAVIOR, Color( 255, 255, 255, 255 ), GetName() ); |
|
me->DebugConColorMsg( NEXTBOT_BEHAVIOR, Color( 255, 255, 255, 255 ), "\n" ); |
|
} |
|
|
|
// these value must be valid before invoking OnStart, in case an OnSuspend happens |
|
m_isStarted = true; |
|
m_actor = me; |
|
m_behavior = behavior; |
|
|
|
// maintain parent/child relationship during transitions |
|
if ( priorAction ) |
|
{ |
|
m_parent = priorAction->m_parent; |
|
} |
|
|
|
if ( m_parent ) |
|
{ |
|
// child pointer of an Action always points to the ACTIVE child |
|
// parent pointers are set when child Actions are instantiated |
|
m_parent->m_child = this; |
|
} |
|
|
|
// maintain stack pointers |
|
m_buriedUnderMe = buriedUnderMeAction; |
|
if ( buriedUnderMeAction ) |
|
{ |
|
buriedUnderMeAction->m_coveringMe = this; |
|
} |
|
|
|
// we are always on top of the stack. if our priorAction was buried, it cleared |
|
// everything covering it when it ended (which happens before we start) |
|
m_coveringMe = NULL; |
|
|
|
// start the optional child action |
|
m_child = InitialContainedAction( me ); |
|
if ( m_child ) |
|
{ |
|
// define initial parent/child relationship |
|
m_child->m_parent = this; |
|
|
|
m_child = m_child->ApplyResult( me, behavior, ChangeTo( m_child, "Starting child Action" ) ); |
|
} |
|
|
|
// start ourselves |
|
ActionResult< Actor > result = OnStart( me, priorAction ); |
|
|
|
return result; |
|
} |
|
|
|
|
|
//------------------------------------------------------------------------------------------- |
|
template < typename Actor > |
|
ActionResult< Actor > Action< Actor >::InvokeUpdate( Actor *me, Behavior< Actor > *behavior, float interval ) |
|
{ |
|
// an explicit "out of scope" check is needed here to prevent any |
|
// pending events causing an out of scope action to linger |
|
if ( IsOutOfScope() ) |
|
{ |
|
// exit self to make this Action active and allow result to take effect on its next Update |
|
return Done( "Out of scope" ); |
|
} |
|
|
|
if ( !m_isStarted ) |
|
{ |
|
// this Action has not yet begun - start it |
|
return ChangeTo( this, "Starting Action" ); |
|
} |
|
|
|
// honor any pending event results |
|
ActionResult< Actor > eventResult = ProcessPendingEvents(); |
|
if ( !eventResult.IsContinue() ) |
|
{ |
|
return eventResult; |
|
} |
|
|
|
// update our child action first, since it has the most specific behavior |
|
if ( m_child ) |
|
{ |
|
m_child = m_child->ApplyResult( me, behavior, m_child->InvokeUpdate( me, behavior, interval ) ); |
|
} |
|
|
|
// update ourselves |
|
ActionResult< Actor > result; |
|
{ |
|
VPROF_BUDGET( GetName(), "NextBot" ); |
|
|
|
result = Update( me, interval ); |
|
} |
|
|
|
return result; |
|
} |
|
|
|
|
|
//------------------------------------------------------------------------------------------- |
|
/** |
|
* This method calls the virtual OnEnd() method for the Action, its children, and Actions |
|
* stacked on top of it. |
|
* It does NOT delete resources, or disturb pointer relationships, because this Action |
|
* needs to remain valid for a short while as an argument to OnStart(), OnSuspend(), etc for |
|
* the next Action. |
|
* The destructor for the Action frees memory for this Action, its children, etc. |
|
*/ |
|
template < typename Actor > |
|
void Action< Actor >::InvokeOnEnd( Actor *me, Behavior< Actor > *behavior, Action< Actor > *nextAction ) |
|
{ |
|
if ( !m_isStarted ) |
|
{ |
|
// we are not started (or never were) |
|
return; |
|
} |
|
|
|
if ( me->IsDebugging( NEXTBOT_BEHAVIOR ) || NextBotDebugHistory.GetBool() ) |
|
{ |
|
me->DebugConColorMsg( NEXTBOT_BEHAVIOR, Color( 255, 255, 150, 255 ), "%3.2f: %s:%s: ", gpGlobals->curtime, me->GetDebugIdentifier(), behavior->GetName() ); |
|
me->DebugConColorMsg( NEXTBOT_BEHAVIOR, Color( 255, 0, 0, 255 ), " ENDING " ); |
|
me->DebugConColorMsg( NEXTBOT_BEHAVIOR, Color( 255, 255, 255, 255 ), GetName() ); |
|
me->DebugConColorMsg( NEXTBOT_BEHAVIOR, Color( 255, 255, 255, 255 ), "\n" ); |
|
} |
|
|
|
// we are no longer started |
|
m_isStarted = false; |
|
|
|
// tell child Action(s) to leave (but don't disturb the list itself) |
|
Action< Actor > *child, *next = NULL; |
|
for( child = m_child; child; child = next ) |
|
{ |
|
next = child->m_buriedUnderMe; |
|
child->InvokeOnEnd( me, behavior, nextAction ); |
|
} |
|
|
|
// leave ourself |
|
OnEnd( me, nextAction ); |
|
|
|
// leave any Actions stacked on top of me |
|
if ( m_coveringMe ) |
|
{ |
|
m_coveringMe->InvokeOnEnd( me, behavior, nextAction ); |
|
} |
|
} |
|
|
|
|
|
//------------------------------------------------------------------------------------------- |
|
/** |
|
* Just invoke OnSuspend - when the interrupting Action is started it will |
|
* update our buried/covered pointers. |
|
* OnSuspend may cause this Action to exit. |
|
*/ |
|
template < typename Actor > |
|
Action< Actor > * Action< Actor >::InvokeOnSuspend( Actor *me, Behavior< Actor > *behavior, Action< Actor > *interruptingAction ) |
|
{ |
|
if ( me->IsDebugging( NEXTBOT_BEHAVIOR ) || NextBotDebugHistory.GetBool() ) |
|
{ |
|
me->DebugConColorMsg( NEXTBOT_BEHAVIOR, Color( 255, 255, 150, 255 ), "%3.2f: %s:%s: ", gpGlobals->curtime, me->GetDebugIdentifier(), behavior->GetName() ); |
|
me->DebugConColorMsg( NEXTBOT_BEHAVIOR, Color( 255, 0, 255, 255 ), " SUSPENDING " ); |
|
me->DebugConColorMsg( NEXTBOT_BEHAVIOR, Color( 255, 255, 255, 255 ), GetName() ); |
|
me->DebugConColorMsg( NEXTBOT_BEHAVIOR, Color( 255, 255, 255, 255 ), "\n" ); |
|
} |
|
|
|
// suspend child Action |
|
if ( m_child ) |
|
{ |
|
m_child = m_child->InvokeOnSuspend( me, behavior, interruptingAction ); |
|
} |
|
|
|
// suspend ourselves |
|
m_isSuspended = true; |
|
ActionResult< Actor > result = OnSuspend( me, interruptingAction ); |
|
|
|
if ( result.IsDone() ) |
|
{ |
|
// we want to be replaced instead of suspended |
|
InvokeOnEnd( me, behavior, NULL ); |
|
|
|
Action< Actor > * buried = GetActionBuriedUnderMe(); |
|
|
|
behavior->DestroyAction( this ); |
|
|
|
// new Action on top of the stack |
|
return buried; |
|
} |
|
|
|
// we are still on top of the stack at this moment |
|
return this; |
|
} |
|
|
|
|
|
//------------------------------------------------------------------------------------------- |
|
template < typename Actor > |
|
ActionResult< Actor > Action< Actor >::InvokeOnResume( Actor *me, Behavior< Actor > *behavior, Action< Actor > *interruptingAction ) |
|
{ |
|
if ( me->IsDebugging( NEXTBOT_BEHAVIOR ) || NextBotDebugHistory.GetBool() ) |
|
{ |
|
me->DebugConColorMsg( NEXTBOT_BEHAVIOR, Color( 255, 255, 150, 255 ), "%3.2f: %s:%s: ", gpGlobals->curtime, me->GetDebugIdentifier(), behavior->GetName() ); |
|
me->DebugConColorMsg( NEXTBOT_BEHAVIOR, Color( 255, 0, 255, 255 ), " RESUMING " ); |
|
me->DebugConColorMsg( NEXTBOT_BEHAVIOR, Color( 255, 255, 255, 255 ), GetName() ); |
|
me->DebugConColorMsg( NEXTBOT_BEHAVIOR, Color( 255, 255, 255, 255 ), "\n" ); |
|
} |
|
|
|
if ( !m_isSuspended ) |
|
{ |
|
// we were never suspended |
|
return Continue(); |
|
} |
|
|
|
if ( m_eventResult.IsRequestingChange() ) |
|
{ |
|
// this Action is not actually being Resumed, because a change |
|
// is already pending from a prior event |
|
return Continue(); |
|
} |
|
|
|
// resume ourselves |
|
m_isSuspended = false; |
|
m_coveringMe = NULL; |
|
|
|
if ( m_parent ) |
|
{ |
|
// we are once again our parent's active child |
|
m_parent->m_child = this; |
|
} |
|
|
|
// resume child Action |
|
if ( m_child ) |
|
{ |
|
m_child = m_child->ApplyResult( me, behavior, m_child->InvokeOnResume( me, behavior, interruptingAction ) ); |
|
} |
|
|
|
// actually resume ourselves |
|
ActionResult< Actor > result = OnResume( me, interruptingAction ); |
|
|
|
return result; |
|
} |
|
|
|
|
|
//------------------------------------------------------------------------------------------- |
|
/** |
|
* Given the result of this Action's work, apply the result to potentially create a new Action |
|
*/ |
|
template < typename Actor > |
|
Action< Actor > *Action< Actor >::ApplyResult( Actor *me, Behavior< Actor > *behavior, ActionResult< Actor > result ) |
|
{ |
|
Action< Actor > *newAction = result.m_action; |
|
|
|
switch( result.m_type ) |
|
{ |
|
//----------------------------------------------------------------------------------------------------- |
|
// transition to new Action |
|
case CHANGE_TO: |
|
{ |
|
if ( newAction == NULL ) |
|
{ |
|
DevMsg( "Error: Attempted CHANGE_TO to a NULL Action\n" ); |
|
AssertMsg( false, "Action: Attempted CHANGE_TO to a NULL Action" ); |
|
return this; |
|
} |
|
|
|
// debug display |
|
if ( me->IsDebugging( NEXTBOT_BEHAVIOR ) || NextBotDebugHistory.GetBool() ) |
|
{ |
|
me->DebugConColorMsg( NEXTBOT_BEHAVIOR, Color( 255, 255, 150, 255 ), "%3.2f: %s:%s: ", gpGlobals->curtime, me->GetDebugIdentifier(), behavior->GetName() ); |
|
|
|
if ( this == newAction ) |
|
{ |
|
me->DebugConColorMsg( NEXTBOT_BEHAVIOR, Color( 255, 0, 0, 255 ), "START " ); |
|
me->DebugConColorMsg( NEXTBOT_BEHAVIOR, Color( 255, 255, 255, 255 ), newAction->GetName() ); |
|
} |
|
else |
|
{ |
|
me->DebugConColorMsg( NEXTBOT_BEHAVIOR, Color( 255, 255, 255, 255 ), this->GetName() ); |
|
me->DebugConColorMsg( NEXTBOT_BEHAVIOR, Color( 255, 0, 0, 255 ), " CHANGE_TO " ); |
|
me->DebugConColorMsg( NEXTBOT_BEHAVIOR, Color( 255, 255, 255, 255 ), newAction->GetName() ); |
|
} |
|
|
|
if ( result.m_reason ) |
|
{ |
|
me->DebugConColorMsg( NEXTBOT_BEHAVIOR, Color( 150, 255, 150, 255 ), " (%s)\n", result.m_reason ); |
|
} |
|
else |
|
{ |
|
me->DebugConColorMsg( NEXTBOT_BEHAVIOR, Color( 255, 255, 255, 255 ), "\n" ); |
|
} |
|
} |
|
|
|
// we are done |
|
this->InvokeOnEnd( me, behavior, newAction ); |
|
|
|
// start the new Action |
|
ActionResult< Actor > startResult = newAction->InvokeOnStart( me, behavior, this, this->m_buriedUnderMe ); |
|
|
|
// discard ended action |
|
if ( this != newAction ) |
|
{ |
|
behavior->DestroyAction( this ); |
|
} |
|
|
|
// debug display |
|
if ( me->IsDebugging( NEXTBOT_BEHAVIOR ) ) |
|
{ |
|
newAction->PrintStateToConsole(); |
|
} |
|
|
|
// apply result of starting the Action |
|
return newAction->ApplyResult( me, behavior, startResult ); |
|
} |
|
|
|
//----------------------------------------------------------------------------------------------------- |
|
// temporarily suspend ourselves for the newAction, covering it on the stack |
|
case SUSPEND_FOR: |
|
{ |
|
// interrupting Action always goes on the TOP of the stack - find it |
|
Action< Actor > *topAction = this; |
|
while ( topAction->m_coveringMe ) |
|
{ |
|
topAction = topAction->m_coveringMe; |
|
} |
|
|
|
// debug display |
|
if ( me->IsDebugging( NEXTBOT_BEHAVIOR ) || NextBotDebugHistory.GetBool() ) |
|
{ |
|
me->DebugConColorMsg( NEXTBOT_BEHAVIOR, Color( 255, 255, 150, 255 ), "%3.2f: %s:%s: ", gpGlobals->curtime, me->GetDebugIdentifier(), behavior->GetName() ); |
|
|
|
me->DebugConColorMsg( NEXTBOT_BEHAVIOR, Color( 255, 255, 255, 255 ), this->GetName() ); |
|
me->DebugConColorMsg( NEXTBOT_BEHAVIOR, Color( 255, 0, 255, 255 ), " caused " ); |
|
me->DebugConColorMsg( NEXTBOT_BEHAVIOR, Color( 255, 255, 255, 255 ), topAction->GetName() ); |
|
me->DebugConColorMsg( NEXTBOT_BEHAVIOR, Color( 255, 0, 255, 255 ), " to SUSPEND_FOR " ); |
|
me->DebugConColorMsg( NEXTBOT_BEHAVIOR, Color( 255, 255, 255, 255 ), newAction->GetName() ); |
|
|
|
if ( result.m_reason ) |
|
{ |
|
me->DebugConColorMsg( NEXTBOT_BEHAVIOR, Color( 150, 255, 150, 255 ), " (%s)\n", result.m_reason ); |
|
} |
|
else |
|
{ |
|
me->DebugConColorMsg( NEXTBOT_BEHAVIOR, Color( 255, 255, 255, 255 ), "\n" ); |
|
} |
|
} |
|
|
|
// suspend the Action we just covered up |
|
topAction = topAction->InvokeOnSuspend( me, behavior, newAction ); |
|
|
|
// begin the interrupting Action. |
|
ActionResult< Actor > startResult = newAction->InvokeOnStart( me, behavior, topAction, topAction ); |
|
|
|
// debug display |
|
if ( me->IsDebugging( NEXTBOT_BEHAVIOR ) ) |
|
{ |
|
newAction->PrintStateToConsole(); |
|
} |
|
|
|
return newAction->ApplyResult( me, behavior, startResult ); |
|
} |
|
|
|
//----------------------------------------------------------------------------------------------------- |
|
case DONE: |
|
{ |
|
// resume buried action |
|
Action< Actor > *resumedAction = this->m_buriedUnderMe; |
|
|
|
// we are finished |
|
this->InvokeOnEnd( me, behavior, resumedAction ); |
|
|
|
// debug display |
|
if ( me->IsDebugging( NEXTBOT_BEHAVIOR ) || NextBotDebugHistory.GetBool() ) |
|
{ |
|
me->DebugConColorMsg( NEXTBOT_BEHAVIOR, Color( 255, 255, 150, 255 ), "%3.2f: %s:%s: ", gpGlobals->curtime, me->GetDebugIdentifier(), behavior->GetName() ); |
|
|
|
me->DebugConColorMsg( NEXTBOT_BEHAVIOR, Color( 255, 255, 255, 255 ), this->GetName() ); |
|
|
|
if ( resumedAction ) |
|
{ |
|
me->DebugConColorMsg( NEXTBOT_BEHAVIOR, Color( 0, 255, 0, 255 ), " DONE, RESUME " ); |
|
me->DebugConColorMsg( NEXTBOT_BEHAVIOR, Color( 255, 255, 255, 255 ), resumedAction->GetName() ); |
|
} |
|
else |
|
{ |
|
me->DebugConColorMsg( NEXTBOT_BEHAVIOR, Color( 0, 255, 0, 255 ), " DONE." ); |
|
} |
|
|
|
if ( result.m_reason ) |
|
{ |
|
me->DebugConColorMsg( NEXTBOT_BEHAVIOR, Color( 150, 255, 150, 255 ), " (%s)\n", result.m_reason ); |
|
} |
|
else |
|
{ |
|
me->DebugConColorMsg( NEXTBOT_BEHAVIOR, Color( 255, 255, 255, 255 ), "\n" ); |
|
} |
|
} |
|
|
|
if ( resumedAction == NULL ) |
|
{ |
|
// all Actions complete |
|
behavior->DestroyAction( this ); |
|
return NULL; |
|
} |
|
|
|
// resume uncovered action |
|
ActionResult< Actor > resumeResult = resumedAction->InvokeOnResume( me, behavior, this ); |
|
|
|
// debug display |
|
if ( me->IsDebugging( NEXTBOT_BEHAVIOR ) ) |
|
{ |
|
resumedAction->PrintStateToConsole(); |
|
} |
|
|
|
// discard ended action |
|
behavior->DestroyAction( this ); |
|
|
|
// apply result of OnResume() |
|
return resumedAction->ApplyResult( me, behavior, resumeResult ); |
|
} |
|
|
|
case CONTINUE: |
|
case SUSTAIN: |
|
default: |
|
{ |
|
// no change, continue the current action next frame |
|
return this; |
|
} |
|
} |
|
} |
|
|
|
|
|
//------------------------------------------------------------------------------------------- |
|
/** |
|
* Propagate events to sub actions |
|
*/ |
|
template < typename Actor > |
|
INextBotEventResponder *Action< Actor >::FirstContainedResponder( void ) const |
|
{ |
|
return GetActiveChildAction(); |
|
} |
|
|
|
template < typename Actor > |
|
INextBotEventResponder *Action< Actor >::NextContainedResponder( INextBotEventResponder *current ) const |
|
{ |
|
return NULL; |
|
} |
|
|
|
|
|
//------------------------------------------------------------------------------------------- |
|
/** |
|
* Return a temporary string describing the current action stack for debugging |
|
*/ |
|
template < typename Actor > |
|
const char *Action< Actor >::DebugString( void ) const |
|
{ |
|
static char str[ 256 ]; |
|
|
|
str[0] = '\000'; |
|
|
|
// find root |
|
const Action< Actor > *root = this; |
|
while ( root->m_parent ) |
|
{ |
|
root = root->m_parent; |
|
} |
|
|
|
return BuildDecoratedName( str, root ); |
|
} |
|
|
|
|
|
//------------------------------------------------------------------------------------------- |
|
template < typename Actor > |
|
char *Action< Actor >::BuildDecoratedName( char *name, const Action< Actor > *action ) const |
|
{ |
|
const int fudge = 256; |
|
|
|
// add the name of the given action |
|
Q_strcat( name, action->GetName(), fudge ); |
|
|
|
// add any contained actions |
|
const Action< Actor > *child = action->GetActiveChildAction(); |
|
if ( child ) |
|
{ |
|
Q_strcat( name, "( ", fudge ); |
|
BuildDecoratedName( name, child ); |
|
Q_strcat( name, " )", fudge ); |
|
} |
|
|
|
// append buried actions |
|
const Action< Actor > *buried = action->GetActionBuriedUnderMe(); |
|
if ( buried ) |
|
{ |
|
Q_strcat( name, "<<", fudge ); |
|
BuildDecoratedName( name, buried ); |
|
} |
|
|
|
return name; |
|
} |
|
|
|
|
|
//------------------------------------------------------------------------------------------- |
|
/** |
|
* Return a temporary string showing the full lineage of this one action |
|
*/ |
|
template < typename Actor > |
|
const char *Action< Actor >::GetFullName( void ) const |
|
{ |
|
const int fudge = 256; |
|
static char str[ fudge ]; |
|
|
|
str[0] = '\000'; |
|
|
|
const int maxStack = 64; |
|
const char *nameStack[ maxStack ]; |
|
int stackIndex = 0; |
|
|
|
for( const Action< Actor > *action = this; |
|
stackIndex < maxStack && action; |
|
action = action->m_parent ) |
|
{ |
|
nameStack[ stackIndex++ ] = action->GetName(); |
|
} |
|
|
|
// assemble name |
|
for( int i = stackIndex-1; i > 0; --i ) |
|
{ |
|
Q_strcat( str, nameStack[ i ], fudge ); |
|
Q_strcat( str, "/", fudge ); |
|
} |
|
|
|
Q_strcat( str, nameStack[ 0 ], fudge ); |
|
|
|
/* |
|
for( int i = 0; i < stackIndex-1; ++i ) |
|
{ |
|
Q_strcat( str, " )", fudge ); |
|
} |
|
*/ |
|
|
|
return str; |
|
} |
|
|
|
|
|
//------------------------------------------------------------------------------------------- |
|
template < typename Actor > |
|
void Action< Actor >::PrintStateToConsole( void ) const |
|
{ |
|
// emit the Behavior name |
|
//ConColorMsg( Color( 255, 255, 255, 255 ), "%s: ", m_behavior->GetName() ); |
|
|
|
// build the state string |
|
const char *msg = DebugString(); |
|
|
|
const int colorCount = 6; |
|
Color colorTable[ colorCount ]; |
|
colorTable[ 0 ].SetColor( 255, 150, 150, 255 ); |
|
colorTable[ 1 ].SetColor( 150, 255, 150, 255 ); |
|
colorTable[ 2 ].SetColor( 150, 150, 255, 255 ); |
|
colorTable[ 3 ].SetColor( 255, 255, 150, 255 ); |
|
colorTable[ 4 ].SetColor( 50, 255, 255, 255 ); |
|
colorTable[ 5 ].SetColor( 255, 150, 255, 255 ); |
|
|
|
// output the color-coded state string |
|
const int maxBufferSize = 256; |
|
char buffer[ maxBufferSize ]; |
|
|
|
int colorIndex = 0; |
|
int buriedLevel = 0; |
|
|
|
char *outMsg = buffer; |
|
for( const char *c = msg; *c != '\000'; ++c ) |
|
{ |
|
*outMsg = *c; |
|
++outMsg; |
|
|
|
if ( *c == '(' ) |
|
{ |
|
*outMsg = '\000'; |
|
|
|
Color color = colorTable[ colorIndex ]; |
|
|
|
if ( buriedLevel ) |
|
{ |
|
// draw buried labels darkly |
|
color.SetColor( color.r() * 0.5, color.g() * 0.5, color.b() * 0.5, 255 ); |
|
++buriedLevel; |
|
} |
|
|
|
//ConColorMsg( color, "%s", buffer ); |
|
DevMsg( "%s", buffer ); |
|
|
|
colorIndex = ( colorIndex + 1 ) % colorCount; |
|
|
|
outMsg = buffer; |
|
} |
|
else if ( *c == ')' ) |
|
{ |
|
// emit the closing paren with next batch |
|
--outMsg; |
|
*outMsg = '\000'; |
|
|
|
Color color = colorTable[ colorIndex ]; |
|
|
|
if ( buriedLevel ) |
|
{ |
|
// draw buried labels darkly |
|
color.SetColor( color.r() * 0.5, color.g() * 0.5, color.b() * 0.5, 255 ); |
|
|
|
--buriedLevel; |
|
} |
|
|
|
//ConColorMsg( color, "%s", buffer ); |
|
DevMsg( "%s", buffer ); |
|
|
|
--colorIndex; |
|
if ( colorIndex < 0 ) |
|
colorIndex = colorCount-1; |
|
|
|
outMsg = buffer; |
|
|
|
*outMsg = ')'; |
|
++outMsg; |
|
} |
|
else if ( *c == '<' && buriedLevel == 0 ) |
|
{ |
|
// caught a "<<" stack push |
|
++c; |
|
|
|
*outMsg = '<'; |
|
++outMsg; |
|
*outMsg = '\000'; |
|
|
|
// output active substring at full brightness |
|
//ConColorMsg( colorTable[ colorIndex ], "%s", buffer ); |
|
DevMsg( "%s", buffer ); |
|
|
|
outMsg = buffer; |
|
|
|
// from here until end of Action, use dim colors |
|
buriedLevel = 1; |
|
} |
|
|
|
} |
|
|
|
*outMsg = '\000'; |
|
//ConColorMsg( colorTable[ colorIndex ], "%s", buffer ); |
|
DevMsg( "%s", buffer ); |
|
|
|
//ConColorMsg( colorTable[ colorIndex ], "\n\n" ); |
|
DevMsg( "\n\n" ); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
#endif // _BEHAVIOR_ENGINE_H_ |
|
|
|
|
|
|
|
|
|
|
|
|