| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475 |
- /*
- ** Command & Conquer Generals(tm)
- ** Copyright 2025 Electronic Arts Inc.
- **
- ** This program is free software: you can redistribute it and/or modify
- ** it under the terms of the GNU General Public License as published by
- ** the Free Software Foundation, either version 3 of the License, or
- ** (at your option) any later version.
- **
- ** This program is distributed in the hope that it will be useful,
- ** but WITHOUT ANY WARRANTY; without even the implied warranty of
- ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- ** GNU General Public License for more details.
- **
- ** You should have received a copy of the GNU General Public License
- ** along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
- ////////////////////////////////////////////////////////////////////////////////
- // //
- // (c) 2001-2003 Electronic Arts Inc. //
- // //
- ////////////////////////////////////////////////////////////////////////////////
- // StateMachine.h
- // Finite state machine encapsulation
- // Author: Michael S. Booth, January 2002
- #pragma once
- #ifndef _STATE_MACHINE_H_
- #define _STATE_MACHINE_H_
- #include "Common/GameMemory.h"
- #include "Common/GameType.h"
- #include "Common/ModelState.h"
- #include "Common/Snapshot.h"
- #include "Common/Xfer.h"
- //-----------------------------------------------------------------------------
- //-----------------------------------------------------------------------------
- //-----------------------------------------------------------------------------
- class State;
- class StateMachine;
- class Object;
- #undef STATE_MACHINE_DEBUG
- #if defined(_DEBUG)
- #define STATE_MACHINE_DEBUG
- #endif
- #if defined(_INTERNAL)
- #define STATE_MACHINE_DEBUG //uncomment to debug state machines in internal. jba.
- #endif
- //-----------------------------------------------------------------------------
- //-----------------------------------------------------------------------------
- //-----------------------------------------------------------------------------
- enum { MACHINE_DONE_STATE_ID = 999998, INVALID_STATE_ID = 999999 };
- typedef UnsignedInt StateID; ///< used to denote individual states
- typedef Bool (*StateTransFuncPtr)( State *state, void* userData );
- /**
- * State return codes
- */
- enum StateReturnType
- {
- // note that all positive values are reserved for STATE_SLEEP!
- STATE_CONTINUE = 0, ///< stay in this state (only for update method)
- STATE_SUCCESS = -1, ///< state finished successfully, go to next state
- STATE_FAILURE = -2, ///< state finished abnormally, go to next state
- };
- #define STATE_SLEEP(numFrames) ((StateReturnType)(numFrames))
- #define IS_STATE_SLEEP(ret) ((Int)(ret) > 0)
- #define GET_STATE_SLEEP_FRAMES(ret) ((UnsignedInt)(ret))
- // (we use 0x3fffffff so that we can add offsets and not overflow...
- // at 30fps that's around ~414 days!)
- #define STATE_SLEEP_FOREVER STATE_SLEEP(0x3fffffff)
- // this is mainly useful for states that enclose other state machines...
- // where, even if the enclosed machine is sleeping, the encloser still needs
- // to run every frame.
- inline StateReturnType CONVERT_SLEEP_TO_CONTINUE(StateReturnType s)
- {
- return IS_STATE_SLEEP(s) ? STATE_CONTINUE : s;
- }
- // this is mainly useful for states that enclose other state machines...
- // where the encloser and enclosee both might sleep. we need to choose the min
- // sleep time that satisfies both.
- inline StateReturnType MIN_SLEEP(UnsignedInt encloserSleep, StateReturnType encloseeResult)
- {
- if (IS_STATE_SLEEP(encloseeResult))
- {
- UnsignedInt encloseeSleep = GET_STATE_SLEEP_FRAMES(encloseeResult);
- return STATE_SLEEP(min(encloserSleep, encloseeSleep));
- }
- else
- {
- // if enclosee needs to stay awake, we better do so, regardless
- return encloseeResult;
- }
- }
- /**
- * Special argument for onCondition. It means when the given condition
- * becomes true, the state machine will exit and return the given status.
- */
- enum
- {
- EXIT_MACHINE_WITH_SUCCESS = 9998,
- EXIT_MACHINE_WITH_FAILURE = 9999
- };
- /**
- * Parameters for onExit().
- */
- enum StateExitType
- {
- EXIT_NORMAL, ///< state exited due to normal state transitioning
- EXIT_RESET ///< state exited due to state machine reset
- };
- struct StateConditionInfo
- {
- StateTransFuncPtr test;
- StateID toStateID;
- void* userData;
- StateConditionInfo(StateTransFuncPtr t, StateID id, void* ud) : test(t), toStateID(id), userData(ud) { }
- };
- //-----------------------------------------------------------------------------
- //-----------------------------------------------------------------------------
- //-----------------------------------------------------------------------------
- /**
- * An abstraction of a machine's "state".
- */
- class State : public MemoryPoolObject, public Snapshot
- {
- MEMORY_POOL_GLUE_ABC( State ) ///< this abstract class needs memory pool hooks
- public:
- State( StateMachine *machine, AsciiString name);
- // already defined by MPO.
- //virtual ~State() { }
- virtual StateReturnType onEnter() { return STATE_CONTINUE; } ///< executed once when entering state
- virtual void onExit( StateExitType status ) { } ///< executed once when leaving state
- virtual StateReturnType update() = 0; ///< implements this state's behavior, decides when to change state
- virtual Bool isIdle() const { return false; }
- virtual Bool isAttack() const { return false; }
- //Definition of busy -- when explicitly in the busy state. Moving or attacking is not considered busy!
- virtual Bool isBusy() const { return false; }
- inline StateMachine* getMachine() { return m_machine; } ///< return the machine this state is part of
- inline StateID getID() const { return m_ID; } ///< get this state's id
- Object* getMachineOwner();
- const Object* getMachineOwner() const;
- Object* getMachineGoalObject();
- const Object* getMachineGoalObject() const;
- const Coord3D* getMachineGoalPosition() const;
- #ifdef STATE_MACHINE_DEBUG
- virtual AsciiString getName() const {return m_name;}
- #endif
- // for internal use by the StateMachine class ---------------------------------------------------------
- inline void friend_setID( StateID id ) { m_ID = id; } ///< define this state's id (for use only by StateMachine class)
- void friend_onSuccess( StateID toStateID ) { m_successStateID = toStateID; } ///< define which state to move to after successful completion
- void friend_onFailure( StateID toStateID ) { m_failureStateID = toStateID; } ///< define which state to move to after failure
- void friend_onCondition( StateTransFuncPtr test, StateID toStateID, void* userData, const char* description = NULL ); ///< define when to change state
- StateReturnType friend_checkForTransitions( StateReturnType status ); ///< given a return code, handle state transitions
- StateReturnType friend_checkForSleepTransitions( StateReturnType status ); ///< given a return code, handle state transitions
- protected:
- #ifdef STATE_MACHINE_DEBUG
- inline void setName(AsciiString n) { m_name = n; }
- #endif
- protected:
- // snapshot interface - pure virtual here.
- // Essentially all the member data gets set up on creation and shouldn't change.
- // So none of it needs to be saved, and it nicely forces all user states to
- // remember to implement crc, xfer & loadPostProcess. jba
- virtual void crc( Xfer *xfer )=0;
- virtual void xfer( Xfer *xfer )=0;
- virtual void loadPostProcess()=0;
- private:
- struct TransitionInfo
- {
- StateTransFuncPtr test; ///< the condition evaluation function
- StateID toStateID; ///< the state to transition to
- void* userData; ///< data passed to transFuncPtr.
- #ifdef STATE_MACHINE_DEBUG
- const char* description; ///< description (for debugging purposes)
- #endif
- TransitionInfo(StateTransFuncPtr t, StateID id, void* ud, const char* desc) :
- test(t),
- toStateID(id),
- userData(ud)
- #ifdef STATE_MACHINE_DEBUG
- , description(desc)
- #endif
- { }
- };
- StateID m_ID; ///< this state's ID
- StateID m_successStateID; ///< state to move to upon success
- StateID m_failureStateID; ///< state to move to upon failure
- std::vector<TransitionInfo> m_transitions; ///< possible transitions from this state
- StateMachine *m_machine; ///< the state machine this state is part of
- protected:
- #ifdef STATE_MACHINE_DEBUG
- AsciiString m_name; ///< Human readable name of this state - for debugging. jba.
- #endif
- };
- inline State::~State() { }
- //-----------------------------------------------------------------------------
- //-----------------------------------------------------------------------------
- //-----------------------------------------------------------------------------
- /**
- * A finite state machine.
- */
- class StateMachine : public MemoryPoolObject, public Snapshot
- {
- MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE( StateMachine, "StateMachinePool" );
- public:
- /**
- * All of the states used by this machine should be
- * instantiated and defined via defineState() in the
- * machine's constructor.
- */
- StateMachine( Object *owner, AsciiString name );
- // virtual destructor defined by MemoryPool
- virtual StateReturnType updateStateMachine(); ///< run one step of the machine
- virtual void clear(); ///< clear the machine's internals to a known, initialized state
- virtual StateReturnType resetToDefaultState(); ///< clear the machine's internals and set to the default state
- /** must be called to jump the StateMachine into the default state... this may fail
- (return a failure code), so it can't be done via the ctor. you MUST call this before
- using the state machine. */
- virtual StateReturnType initDefaultState();
- virtual StateReturnType setState( StateID newStateID ); ///< change the current state of the machine (which may cause further state changes, due to onEnter)
- StateID getCurrentStateID() const { return m_currentState ? m_currentState->getID() : INVALID_STATE_ID; } ///< return the id of the current state of the machine
- Bool isInIdleState() const { return m_currentState ? m_currentState->isIdle() : true; } // stateless things are considered 'idle'
- Bool isInAttackState() const { return m_currentState ? m_currentState->isAttack() : true; } // stateless things are considered 'idle'
- Bool isInForceAttackState() const { return m_currentState ? m_currentState->isIdle() : true; } // stateless things are considered 'idle'
- //Definition of busy -- when explicitly in the busy state. Moving or attacking is not considered busy!
- Bool isInBusyState() const { return m_currentState ? m_currentState->isBusy() : false; } // stateless things are not considered 'busy'
- // no, this is now deprecated. you should strive to avoid having to get the current state.
- // try to make do with getCurrentStateID() or isInIdleState() instead. (srj)
- // State *getCurrentState() { return m_currentState; } ///< return the current state of the machine
- /**
- * Lock/unlock this state machine.
- * If a machine is locked, it cannot be reset, or given external setStates(), etc.
- */
- void lock(const char* msg)
- {
- m_locked = true;
- #ifdef STATE_MACHINE_DEBUG
- m_lockedby = msg;
- #endif
- }
- void unlock()
- {
- m_locked = false;
- #ifdef STATE_MACHINE_DEBUG
- m_lockedby = NULL;
- #endif
- }
- Bool isLocked() const { return m_locked; }
- /**
- * Get the object that "owns" this machine.
- * There need not be an object with a machine, but it is so common
- * that it is useful to have this method in the generic state machine.
- */
- Object *getOwner() { return m_owner; }
- const Object *getOwner() const { return m_owner; }
- // common parameters for state machines
- void setGoalObject( const Object *obj );
- Object *getGoalObject();
- const Object *getGoalObject() const;
- void setGoalPosition( const Coord3D *pos );
- const Coord3D *getGoalPosition() const { return &m_goalPosition; }
- Bool isGoalObjectDestroyed() const; ///< Returns true if we had a goal object, but it has been destroyed.
-
- virtual void halt(void); ///< Stops the state machine & disables it in preparation for deleting it.
- //
- // The following methods are for internal use by the State class
- //
- StateReturnType internalSetState( StateID newStateID ); ///< for internal use only - change the current state of the machine
- #if defined(_DEBUG) || defined(_INTERNAL)
- UnsignedInt peekSleepTill() const { return m_sleepTill; }
- #endif
- #ifdef STATE_MACHINE_DEBUG
- Bool getWantsDebugOutput() const;
- void setDebugOutput( Bool output ) { m_debugOutput = output; }
- void setName( AsciiString name) {m_name = name;}
- inline AsciiString getName() const {return m_name;}
- virtual AsciiString getCurrentStateName() const { return m_currentState ? m_currentState->getName() : AsciiString::TheEmptyString;}
- #else
- inline Bool getWantsDebugOutput() const { return false; }
- inline AsciiString getCurrentStateName() const { return AsciiString::TheEmptyString;}
- #endif
- protected:
- // snapshot interface
- virtual void crc( Xfer *xfer );
- virtual void xfer( Xfer *xfer );
- virtual void loadPostProcess();
- protected:
- /**
- * Given a unique (to this machine) integer ID representing a state, and an instance
- * of that state, the machine records this as a possible state, and
- * internally maps the given integer ID to the state instance.
- * These state id's are used to change the machine's state via setState().
- */
- void defineState( StateID id, State *state,
- StateID successID,
- StateID failureID,
- const StateConditionInfo* conditions = NULL);
- State* internalGetState( StateID id );
- private:
- void internalClear();
- void internalSetGoalObject( const Object *obj );
- void internalSetGoalPosition( const Coord3D *pos);
- std::map<StateID, State *> m_stateMap; ///< the mapping of ids to states
- Object* m_owner; ///< object that "owns" this machine
- UnsignedInt m_sleepTill; ///< if nonzero, we are sleeping 'till this frame
- StateID m_defaultStateID; ///< the default state of the machine
- State* m_currentState;
- ObjectID m_goalObjectID; ///< the object of interest for this state
- Coord3D m_goalPosition; ///< the position of interest for this state
- Bool m_locked; ///< whether this machine is locked or not
- Bool m_defaultStateInited; ///< if initDefaultState has been called
- #ifdef STATE_MACHINE_DEBUG
- Bool m_debugOutput;
- AsciiString m_name; ///< Human readable name of this state - for debugging. jba.
- const char* m_lockedby;
- #endif
- };
- //-----------------------------------------------------------------------------
- inline Object* State::getMachineOwner() { return m_machine->getOwner(); }
- inline const Object* State::getMachineOwner() const { return m_machine->getOwner(); }
- inline Object* State::getMachineGoalObject() { return m_machine->getGoalObject(); } ///< return the machine this state is part of
- inline const Object* State::getMachineGoalObject() const { return m_machine->getGoalObject(); } ///< return the machine this state is part of
- inline const Coord3D* State::getMachineGoalPosition() const { return m_machine->getGoalPosition(); } ///< return the machine this state is part of
- //-----------------------------------------------------------------------------------------------------------
- /**
- A utility state that immediately succeeds.
- */
- class SuccessState : public State
- {
- MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE(SuccessState, "SuccessState")
- public:
- SuccessState( StateMachine *machine ) : State( machine, "SuccessState") { }
- virtual StateReturnType onEnter() { return STATE_SUCCESS; }
- virtual StateReturnType update() { return STATE_SUCCESS; }
- protected:
- // snapshot interface STUBBED.
- virtual void crc( Xfer *xfer ){};
- virtual void xfer( Xfer *xfer ){XferVersion cv = 1; XferVersion v = cv; xfer->xferVersion( &v, cv );}
- virtual void loadPostProcess(){};
- };
- EMPTY_DTOR(SuccessState)
- //-----------------------------------------------------------------------------------------------------------
- /**
- A utility state that immediately fails.
- */
- class FailureState : public State
- {
- MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE(FailureState, "FailureState")
- public:
- FailureState( StateMachine *machine ) : State( machine, "FailureState") { }
- virtual StateReturnType onEnter() { return STATE_FAILURE; }
- virtual StateReturnType update() { return STATE_FAILURE; }
- protected:
- // snapshot interface STUBBED.
- virtual void crc( Xfer *xfer ){};
- virtual void xfer( Xfer *xfer ){XferVersion cv = 1; XferVersion v = cv; xfer->xferVersion( &v, cv );}
- virtual void loadPostProcess(){};
- };
- EMPTY_DTOR(FailureState)
-
- //-----------------------------------------------------------------------------------------------------------
- /**
- A utility state that never exits (except due to conditions).
- */
- class ContinueState : public State
- {
- MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE(ContinueState, "ContinueState")
- public:
- ContinueState( StateMachine *machine ) : State( machine, "ContinueState" ) { }
- virtual StateReturnType onEnter() { return STATE_CONTINUE; }
- virtual StateReturnType update() { return STATE_CONTINUE; }
- protected:
- // snapshot interface STUBBED.
- virtual void crc( Xfer *xfer ){};
- virtual void xfer( Xfer *xfer ){XferVersion cv = 1; XferVersion v = cv; xfer->xferVersion( &v, cv );}
- virtual void loadPostProcess(){};
- };
- EMPTY_DTOR(ContinueState)
- //-----------------------------------------------------------------------------------------------------------
- /**
- A utility state that sleeps forever.
- */
- class SleepState : public State
- {
- MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE(SleepState, "SleepState")
- public:
- SleepState( StateMachine *machine ) : State( machine, "SleepState" ) { }
- virtual StateReturnType onEnter() { return STATE_SLEEP_FOREVER; }
- virtual StateReturnType update() { return STATE_SLEEP_FOREVER; }
- protected:
- // snapshot interface STUBBED.
- virtual void crc( Xfer *xfer ){};
- virtual void xfer( Xfer *xfer ){XferVersion cv = 1; XferVersion v = cv; xfer->xferVersion( &v, cv );}
- virtual void loadPostProcess(){};
- };
- EMPTY_DTOR(SleepState)
- #endif // _STATE_MACHINE_H_
|