/* ** 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 . */ //////////////////////////////////////////////////////////////////////////////// // // // (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 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 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_