Initial commit of Command & Conquer Generals and Command & Conquer Generals Zero Hour source code.
This commit is contained in:
parent
2e338c00cb
commit
3d0ee53a05
6072 changed files with 2283311 additions and 0 deletions
901
GeneralsMD/Code/GameEngine/Source/Common/StateMachine.cpp
Normal file
901
GeneralsMD/Code/GameEngine/Source/Common/StateMachine.cpp
Normal file
|
@ -0,0 +1,901 @@
|
|||
/*
|
||||
** Command & Conquer Generals Zero Hour(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.cpp
|
||||
// Implementation of basic state machine
|
||||
// Author: Michael S. Booth, January 2002
|
||||
|
||||
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
|
||||
|
||||
#include "Common/Errors.h"
|
||||
#include "Common/StateMachine.h"
|
||||
#include "Common/ThingTemplate.h"
|
||||
#include "Common/GameState.h"
|
||||
#include "Common/GlobalData.h"
|
||||
#include "Common/Xfer.h"
|
||||
#include "GameLogic/GameLogic.h"
|
||||
#include "GameLogic/Object.h"
|
||||
|
||||
#ifdef _INTERNAL
|
||||
// for occasional debugging...
|
||||
|
||||
//#pragma optimize("", off)
|
||||
//#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes")
|
||||
#endif
|
||||
|
||||
//------------------------------------------------------------------------------ Performance Timers
|
||||
//#include "Common/PerfMetrics.h"
|
||||
//#include "Common/PerfTimer.h"
|
||||
|
||||
//static PerfTimer s_stateMachineTimer("StateMachine::update", false, PERFMETRICS_LOGIC_STARTFRAME, PERFMETRICS_LOGIC_STOPFRAME);
|
||||
//-------------------------------------------------------------------------------------------------
|
||||
//-----------------------------------------------------------------------------
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
State::State( StateMachine *machine, AsciiString name )
|
||||
#ifdef STATE_MACHINE_DEBUG
|
||||
: m_name(name)
|
||||
#endif
|
||||
{
|
||||
m_ID = INVALID_STATE_ID;
|
||||
m_successStateID = INVALID_STATE_ID;
|
||||
m_failureStateID = INVALID_STATE_ID;
|
||||
m_machine = machine;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
/**
|
||||
* Add another state transition condition for this state
|
||||
*/
|
||||
void State::friend_onCondition( StateTransFuncPtr test, StateID toStateID, void* userData, const char* description )
|
||||
{
|
||||
m_transitions.push_back(TransitionInfo(test, toStateID, userData, description));
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
class StIncrementer
|
||||
{
|
||||
private:
|
||||
Int& num;
|
||||
public:
|
||||
StIncrementer(Int& n) : num(n)
|
||||
{
|
||||
++num;
|
||||
}
|
||||
~StIncrementer()
|
||||
{
|
||||
--num;
|
||||
}
|
||||
};
|
||||
#ifdef STATE_MACHINE_DEBUG
|
||||
//-----------------------------------------------------------------------------
|
||||
std::vector<StateID> * State::getTransitions( void )
|
||||
{
|
||||
std::vector<StateID> *ids = new std::vector<StateID>;
|
||||
ids->push_back(m_successStateID);
|
||||
ids->push_back(m_failureStateID);
|
||||
// check transition condition list
|
||||
if (!m_transitions.empty())
|
||||
{
|
||||
for(std::vector<TransitionInfo>::const_iterator it = m_transitions.begin(); it != m_transitions.end(); ++it)
|
||||
{
|
||||
ids->push_back(it->toStateID);
|
||||
}
|
||||
}
|
||||
return ids;
|
||||
}
|
||||
#endif
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
/**
|
||||
* Given a return code, handle state transitions
|
||||
*/
|
||||
StateReturnType State::friend_checkForTransitions( StateReturnType status )
|
||||
{
|
||||
static Int checkfortransitionsnum = 0;
|
||||
|
||||
StIncrementer inc(checkfortransitionsnum);
|
||||
if (checkfortransitionsnum >= 20)
|
||||
{
|
||||
DEBUG_CRASH(("checkfortransitionsnum is > 20"));
|
||||
return STATE_FAILURE;
|
||||
}
|
||||
|
||||
DEBUG_ASSERTCRASH(!IS_STATE_SLEEP(status), ("Please handle sleep states prior to this"));
|
||||
|
||||
// handle transitions
|
||||
switch( status )
|
||||
{
|
||||
case STATE_SUCCESS:
|
||||
// check if machine should exit
|
||||
if (m_successStateID == EXIT_MACHINE_WITH_SUCCESS)
|
||||
{
|
||||
getMachine()->internalSetState( MACHINE_DONE_STATE_ID );
|
||||
return STATE_SUCCESS;
|
||||
}
|
||||
else if (m_successStateID == EXIT_MACHINE_WITH_FAILURE)
|
||||
{
|
||||
getMachine()->internalSetState( MACHINE_DONE_STATE_ID );
|
||||
return STATE_FAILURE;
|
||||
}
|
||||
|
||||
// move to new state
|
||||
return getMachine()->internalSetState( m_successStateID );
|
||||
|
||||
case STATE_FAILURE:
|
||||
// check if machine should exit
|
||||
if (m_failureStateID == EXIT_MACHINE_WITH_SUCCESS)
|
||||
{
|
||||
getMachine()->internalSetState( MACHINE_DONE_STATE_ID );
|
||||
return STATE_SUCCESS;
|
||||
}
|
||||
else if (m_failureStateID == EXIT_MACHINE_WITH_FAILURE)
|
||||
{
|
||||
getMachine()->internalSetState( MACHINE_DONE_STATE_ID );
|
||||
return STATE_FAILURE;
|
||||
}
|
||||
|
||||
// move to new state
|
||||
return getMachine()->internalSetState( m_failureStateID );
|
||||
|
||||
case STATE_CONTINUE:
|
||||
|
||||
// check transition condition list
|
||||
if (!m_transitions.empty())
|
||||
{
|
||||
for(std::vector<TransitionInfo>::const_iterator it = m_transitions.begin(); it != m_transitions.end(); ++it)
|
||||
{
|
||||
if (it->test( this, it->userData ))
|
||||
{
|
||||
// test returned true, change to associated state
|
||||
|
||||
#ifdef STATE_MACHINE_DEBUG
|
||||
if (getMachine()->getWantsDebugOutput())
|
||||
{
|
||||
DEBUG_LOG(("%d '%s' -- '%s' condition '%s' returned true!\n", TheGameLogic->getFrame(), getMachineOwner()->getTemplate()->getName().str(),
|
||||
getMachine()->getName().str(), it->description ? it->description : "[no description]"));
|
||||
}
|
||||
#endif
|
||||
|
||||
// check if machine should exit
|
||||
if (it->toStateID == EXIT_MACHINE_WITH_SUCCESS)
|
||||
{
|
||||
return STATE_SUCCESS;
|
||||
}
|
||||
else if (it->toStateID == EXIT_MACHINE_WITH_FAILURE)
|
||||
{
|
||||
return STATE_FAILURE;//Lorenzen wants to know why...
|
||||
}
|
||||
|
||||
// move to new state
|
||||
return getMachine()->internalSetState( it->toStateID );
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// the machine keeps running
|
||||
return STATE_CONTINUE;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
/**
|
||||
* Given a return code, handle state transitions
|
||||
*/
|
||||
StateReturnType State::friend_checkForSleepTransitions( StateReturnType status )
|
||||
{
|
||||
static Int checkfortransitionsnum = 0;
|
||||
|
||||
StIncrementer inc(checkfortransitionsnum);
|
||||
if (checkfortransitionsnum >= 20)
|
||||
{
|
||||
DEBUG_CRASH(("checkforsleeptransitionsnum is > 20"));
|
||||
return STATE_FAILURE;
|
||||
}
|
||||
|
||||
DEBUG_ASSERTCRASH(IS_STATE_SLEEP(status), ("Please only pass sleep states here"));
|
||||
|
||||
// check transition condition list
|
||||
if (m_transitions.empty())
|
||||
return status;
|
||||
|
||||
for(std::vector<TransitionInfo>::const_iterator it = m_transitions.begin(); it != m_transitions.end(); ++it)
|
||||
{
|
||||
if (!it->test( this, it->userData ))
|
||||
continue;
|
||||
|
||||
// test returned true, change to associated state
|
||||
|
||||
#ifdef STATE_MACHINE_DEBUG
|
||||
if (getMachine()->getWantsDebugOutput())
|
||||
{
|
||||
DEBUG_LOG(("%d '%s' -- '%s' condition '%s' returned true!\n", TheGameLogic->getFrame(), getMachineOwner()->getTemplate()->getName().str(),
|
||||
getMachine()->getName().str(), it->description ? it->description : "[no description]"));
|
||||
}
|
||||
#endif
|
||||
|
||||
// check if machine should exit
|
||||
if (it->toStateID == EXIT_MACHINE_WITH_SUCCESS)
|
||||
{
|
||||
return STATE_SUCCESS;
|
||||
}
|
||||
else if (it->toStateID == EXIT_MACHINE_WITH_FAILURE)
|
||||
{
|
||||
return STATE_FAILURE;
|
||||
}
|
||||
else
|
||||
{
|
||||
// move to new state
|
||||
return getMachine()->internalSetState( it->toStateID );
|
||||
}
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
//-----------------------------------------------------------------------------
|
||||
//-----------------------------------------------------------------------------
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
StateMachine::StateMachine( Object *owner, AsciiString name )
|
||||
{
|
||||
m_owner = owner;
|
||||
m_sleepTill = 0;
|
||||
m_defaultStateID = INVALID_STATE_ID;
|
||||
m_defaultStateInited = false;
|
||||
m_currentState = NULL;
|
||||
m_locked = false;
|
||||
#ifdef STATE_MACHINE_DEBUG
|
||||
m_name = name;
|
||||
m_debugOutput = false;
|
||||
m_lockedby = NULL;
|
||||
#endif
|
||||
internalClear();
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
/**
|
||||
* Destructor. Destroy any states attached to this machine.
|
||||
*/
|
||||
StateMachine::~StateMachine()
|
||||
{
|
||||
|
||||
// do not allow current state to exit
|
||||
if (m_currentState)
|
||||
m_currentState->onExit( EXIT_RESET );
|
||||
|
||||
std::map<StateID, State *>::iterator i;
|
||||
|
||||
// delete all states in the mapping
|
||||
for( i = m_stateMap.begin(); i != m_stateMap.end(); ++i )
|
||||
{
|
||||
if ((*i).second)
|
||||
(*i).second->deleteInstance();
|
||||
}
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
#ifdef STATE_MACHINE_DEBUG
|
||||
Bool StateMachine::getWantsDebugOutput() const
|
||||
{
|
||||
if (m_debugOutput)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (TheGlobalData->m_stateMachineDebug)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
#ifdef DEBUG_OBJECT_ID_EXISTS
|
||||
if (TheObjectIDToDebug != 0 && getOwner() != NULL && getOwner()->getID() == TheObjectIDToDebug)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
/**
|
||||
* Clear the internal variables of state machine to known values.
|
||||
*/
|
||||
void StateMachine::internalClear()
|
||||
{
|
||||
m_goalObjectID = INVALID_ID;
|
||||
m_goalPosition.x = 0.0f;
|
||||
m_goalPosition.y = 0.0f;
|
||||
m_goalPosition.z = 0.0f;
|
||||
#ifdef STATE_MACHINE_DEBUG
|
||||
if (getWantsDebugOutput())
|
||||
{
|
||||
DEBUG_LOG(("%d '%s'%x -- '%s' %x internalClear()\n", TheGameLogic->getFrame(), m_owner->getTemplate()->getName().str(), m_owner, m_name.str(), this));
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
/**
|
||||
* Clear the machine
|
||||
*/
|
||||
void StateMachine::clear()
|
||||
{
|
||||
// if the machine is locked, it cannot be cleared
|
||||
if (m_locked)
|
||||
{
|
||||
#ifdef STATE_MACHINE_DEBUG
|
||||
if (m_currentState) DEBUG_LOG((" cur state '%s'\n", m_currentState->getName().str()));
|
||||
DEBUG_LOG(("machine is locked (by %s), cannot be cleared (Please don't ignore; this generally indicates a potential logic flaw)\n",m_lockedby));
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
|
||||
// invoke the old state's onExit()
|
||||
if (m_currentState)
|
||||
m_currentState->onExit( EXIT_RESET );
|
||||
|
||||
m_currentState = NULL;
|
||||
|
||||
internalClear();
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
/**
|
||||
* Reset the machine to its default state
|
||||
*/
|
||||
StateReturnType StateMachine::resetToDefaultState()
|
||||
{
|
||||
// if the machine is locked, it cannot be reset
|
||||
if (m_locked)
|
||||
{
|
||||
#ifdef STATE_MACHINE_DEBUG
|
||||
if (m_currentState) DEBUG_LOG((" cur state '%s'\n", m_currentState->getName().str()));
|
||||
DEBUG_LOG(("machine is locked (by %s), cannot be cleared (Please don't ignore; this generally indicates a potential logic flaw)\n",m_lockedby));
|
||||
#endif
|
||||
return STATE_FAILURE;
|
||||
}
|
||||
|
||||
if (!m_defaultStateInited)
|
||||
{
|
||||
DEBUG_CRASH(("you may not call resetToDefaultState before initDefaultState"));
|
||||
return STATE_FAILURE;
|
||||
}
|
||||
|
||||
// allow current state to exit with EXIT_RESET if present
|
||||
if (m_currentState)
|
||||
m_currentState->onExit( EXIT_RESET );
|
||||
m_currentState = NULL;
|
||||
|
||||
//
|
||||
// the current state has done an onExit, clear the internal guts before we set
|
||||
// the new state, to clear it after the new state is set might be overwriting
|
||||
// things the new state transition causes to happen
|
||||
//
|
||||
internalClear();
|
||||
|
||||
// change to the default state
|
||||
StateReturnType status = internalSetState( m_defaultStateID );
|
||||
|
||||
DEBUG_ASSERTCRASH( status != STATE_FAILURE, ( "StateMachine::resetToDefaultState() Error setting default state" ) );
|
||||
|
||||
return status;
|
||||
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
/**
|
||||
* Run one step of the machine
|
||||
*/
|
||||
StateReturnType StateMachine::updateStateMachine()
|
||||
{
|
||||
UnsignedInt now = TheGameLogic->getFrame();
|
||||
if (m_sleepTill != 0 && now < m_sleepTill)
|
||||
{
|
||||
if( m_currentState == NULL )
|
||||
{
|
||||
return STATE_FAILURE;
|
||||
}
|
||||
return m_currentState->friend_checkForSleepTransitions( STATE_SLEEP(m_sleepTill - now) );
|
||||
}
|
||||
|
||||
// not sleeping anymore
|
||||
m_sleepTill = 0;
|
||||
|
||||
if (m_currentState)
|
||||
{
|
||||
// update() can change m_currentState, so save it for a moment...
|
||||
State* stateBeforeUpdate = m_currentState;
|
||||
|
||||
// execute this state
|
||||
StateReturnType status = m_currentState->update();
|
||||
|
||||
// it is possible that the state's update() method may cause the state to be destroyed
|
||||
if (m_currentState == NULL)
|
||||
{
|
||||
return STATE_FAILURE;
|
||||
}
|
||||
|
||||
// here's the scenario:
|
||||
// -- State A calls foo() and then says "sleep for 2000 frames".
|
||||
// -- however, foo() called setState() to State B. thus our current state is not the same.
|
||||
// -- thus, if the state changed, we must ignore any sleep result and pretend we got STATE_CONTINUE,
|
||||
// so that the new state will be called immediately.
|
||||
if (stateBeforeUpdate != m_currentState)
|
||||
{
|
||||
status = STATE_CONTINUE;
|
||||
}
|
||||
|
||||
if (IS_STATE_SLEEP(status))
|
||||
{
|
||||
// hey, we're sleepy!
|
||||
m_sleepTill = now + GET_STATE_SLEEP_FRAMES(status);
|
||||
return m_currentState->friend_checkForSleepTransitions( STATE_SLEEP(m_sleepTill - now) );
|
||||
}
|
||||
else
|
||||
{
|
||||
// check for state transitions, possibly exiting this machine
|
||||
return m_currentState->friend_checkForTransitions( status );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
DEBUG_CRASH(("State machine has no current state -- did you remember to call initDefaultState?"));
|
||||
return STATE_FAILURE;
|
||||
}
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
/**
|
||||
* Given a unique (for this machine) id number, and an instance of the
|
||||
* State class, the machine records this as a possible state, and
|
||||
* retains the id mapping.
|
||||
* These state id's are used to change the machine's state via setState().
|
||||
*/
|
||||
void StateMachine::defineState( StateID id, State *state, StateID successID, StateID failureID, const StateConditionInfo* conditions )
|
||||
{
|
||||
#ifdef STATE_MACHINE_DEBUG
|
||||
DEBUG_ASSERTCRASH(m_stateMap.find( id ) == m_stateMap.end(), ("duplicate state ID in statemachine %s\n",m_name.str()));
|
||||
#endif
|
||||
|
||||
// map the ID to the state
|
||||
m_stateMap.insert( std::map<StateID, State *>::value_type( id, state ) );
|
||||
|
||||
// store the ID in the state itself, as well
|
||||
state->friend_setID( id );
|
||||
|
||||
state->friend_onSuccess(successID);
|
||||
state->friend_onFailure(failureID);
|
||||
|
||||
while (conditions && conditions->test != NULL)
|
||||
{
|
||||
state->friend_onCondition(conditions->test, conditions->toStateID, conditions->userData);
|
||||
++conditions;
|
||||
}
|
||||
|
||||
if (m_defaultStateID == INVALID_STATE_ID)
|
||||
m_defaultStateID = id;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
/**
|
||||
* Given a state ID, return the state instance
|
||||
*/
|
||||
State *StateMachine::internalGetState( StateID id )
|
||||
{
|
||||
// locate the actual state associated with the given ID
|
||||
std::map<StateID, State *>::iterator i;
|
||||
i = m_stateMap.find( id );
|
||||
|
||||
if (i == m_stateMap.end())
|
||||
{
|
||||
DEBUG_CRASH( ("StateMachine::internalGetState(): Invalid state for object %s using state %d", m_owner->getTemplate()->getName().str(), id) );
|
||||
DEBUG_LOG(("Transisioning to state #d\n", (Int)id));
|
||||
DEBUG_LOG(("Attempting to recover - locating default state...\n"));
|
||||
i = m_stateMap.find(m_defaultStateID);
|
||||
if (i == m_stateMap.end()) {
|
||||
DEBUG_LOG(("Failed to located default state. Aborting...\n"));
|
||||
throw ERROR_BAD_ARG;
|
||||
} else {
|
||||
DEBUG_LOG(("Located default state to recover.\n"));
|
||||
}
|
||||
}
|
||||
|
||||
return (*i).second;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
/**
|
||||
* Change the current state of the machine.
|
||||
* This causes the old state's onExit() method to be invoked,
|
||||
* and the new state's onEnter() method to be invoked.
|
||||
*/
|
||||
StateReturnType StateMachine::setState( StateID newStateID )
|
||||
{
|
||||
// if the machine is locked, it cannot change state via external events
|
||||
if (m_locked)
|
||||
{
|
||||
#ifdef STATE_MACHINE_DEBUG
|
||||
if (m_currentState) DEBUG_LOG((" cur state '%s'\n", m_currentState->getName().str()));
|
||||
DEBUG_LOG(("machine is locked (by %s), cannot be cleared (Please don't ignore; this generally indicates a potential logic flaw)\n",m_lockedby));
|
||||
#endif
|
||||
return STATE_CONTINUE;
|
||||
}
|
||||
|
||||
return internalSetState( newStateID );
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
/**
|
||||
* Change the current state of the machine.
|
||||
* This causes the old state's onExit() method to be invoked,
|
||||
* and the new state's onEnter() method to be invoked.
|
||||
*/
|
||||
StateReturnType StateMachine::internalSetState( StateID newStateID )
|
||||
{
|
||||
State *newState = NULL;
|
||||
|
||||
// anytime the state changes, stop sleeping
|
||||
m_sleepTill = 0;
|
||||
|
||||
// if we're not setting the "done" state ID we will continue with the actual transition
|
||||
if( newStateID != MACHINE_DONE_STATE_ID )
|
||||
{
|
||||
|
||||
// if incoming state is invalid, go to the machine's default state
|
||||
if (newStateID == INVALID_STATE_ID)
|
||||
{
|
||||
newStateID = m_defaultStateID;
|
||||
if (newStateID == INVALID_STATE_ID)
|
||||
{
|
||||
DEBUG_CRASH(("you may NEVER set the current state to an invalid state id."));
|
||||
return STATE_FAILURE;
|
||||
}
|
||||
}
|
||||
|
||||
// extract the state associated with the given ID
|
||||
newState = internalGetState( newStateID );
|
||||
#ifdef STATE_MACHINE_DEBUG
|
||||
if (getWantsDebugOutput())
|
||||
{
|
||||
StateID curState = INVALID_STATE_ID;
|
||||
if (m_currentState) {
|
||||
curState = m_currentState->getID();
|
||||
}
|
||||
DEBUG_LOG(("%d '%s'%x -- '%s' %x exit ", TheGameLogic->getFrame(), m_owner->getTemplate()->getName().str(), m_owner, m_name.str(), this));
|
||||
if (m_currentState) {
|
||||
DEBUG_LOG((" '%s' ", m_currentState->getName().str()));
|
||||
} else {
|
||||
DEBUG_LOG((" INVALID_STATE_ID "));
|
||||
}
|
||||
if (newState) {
|
||||
DEBUG_LOG(("enter '%s' \n", newState->getName().str()));
|
||||
} else {
|
||||
DEBUG_LOG(("to INVALID_STATE\n"));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
// invoke the old state's onExit()
|
||||
if (m_currentState)
|
||||
m_currentState->onExit( EXIT_NORMAL );
|
||||
|
||||
// set the new state
|
||||
m_currentState = newState;
|
||||
|
||||
// invoke the new state's onEnter()
|
||||
/// @todo It might be useful to pass the old state in... (MSB)
|
||||
if( m_currentState )
|
||||
{
|
||||
// onEnter() could conceivably change m_currentState, so save it for a moment...
|
||||
State* stateBeforeEnter = m_currentState;
|
||||
|
||||
StateReturnType status = m_currentState->onEnter();
|
||||
|
||||
// it is possible that the state's onEnter() method may cause the state to be destroyed
|
||||
if (m_currentState == NULL)
|
||||
{
|
||||
return STATE_FAILURE;
|
||||
}
|
||||
|
||||
// here's the scenario:
|
||||
// -- State A calls foo() and then says "sleep for 2000 frames".
|
||||
// -- however, foo() called setState() to State B. thus our current state is not the same.
|
||||
// -- thus, if the state changed, we must ignore any sleep result and pretend we got STATE_CONTINUE,
|
||||
// so that the new state will be called immediately.
|
||||
if (stateBeforeEnter != m_currentState)
|
||||
{
|
||||
status = STATE_CONTINUE;
|
||||
}
|
||||
|
||||
if (IS_STATE_SLEEP(status))
|
||||
{
|
||||
// hey, we're sleepy!
|
||||
UnsignedInt now = TheGameLogic->getFrame();
|
||||
m_sleepTill = now + GET_STATE_SLEEP_FRAMES(status);
|
||||
return m_currentState->friend_checkForSleepTransitions( STATE_SLEEP(m_sleepTill - now) );
|
||||
}
|
||||
else
|
||||
{
|
||||
// check for state transitions, possibly exiting this machine
|
||||
return m_currentState->friend_checkForTransitions( status );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return STATE_CONTINUE; // irrelevant return code, but we must return something
|
||||
}
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
/**
|
||||
* Define the default state of the machine, and
|
||||
* set the machine's state to it.
|
||||
*/
|
||||
StateReturnType StateMachine::initDefaultState()
|
||||
{
|
||||
#if defined(_DEBUG) || defined(_INTERNAL)
|
||||
#ifdef STATE_MACHINE_DEBUG
|
||||
#define REALLY_VERBOSE_LOG(x) /* */
|
||||
// Run through all the transitions and make sure there aren't any transitions to undefined states. jba. [8/18/2003]
|
||||
std::map<StateID, State *>::iterator i;
|
||||
REALLY_VERBOSE_LOG(("SM_BEGIN\n"));
|
||||
for( i = m_stateMap.begin(); i != m_stateMap.end(); ++i ) {
|
||||
State *state = (*i).second;
|
||||
StateID id = state->getID();
|
||||
// Check transitions. [8/18/2003]
|
||||
std::vector<StateID> *ids = state->getTransitions();
|
||||
// check transitions
|
||||
REALLY_VERBOSE_LOG(("State %s(%d) : ", state->getName().str(), id));
|
||||
if (!ids->empty())
|
||||
{
|
||||
for(std::vector<StateID>::const_iterator it = ids->begin(); it != ids->end(); ++it)
|
||||
{
|
||||
StateID curID = *it;
|
||||
REALLY_VERBOSE_LOG(("%d('", curID));
|
||||
if (curID == INVALID_STATE_ID) {
|
||||
REALLY_VERBOSE_LOG(("INVALID_STATE_ID', "));
|
||||
continue;
|
||||
}
|
||||
if (curID == EXIT_MACHINE_WITH_SUCCESS) {
|
||||
REALLY_VERBOSE_LOG(("EXIT_MACHINE_WITH_SUCCESS', "));
|
||||
continue;
|
||||
}
|
||||
if (curID == EXIT_MACHINE_WITH_FAILURE) {
|
||||
REALLY_VERBOSE_LOG(("EXIT_MACHINE_WITH_FAILURE', "));
|
||||
continue;
|
||||
}
|
||||
// locate the actual state associated with the given ID
|
||||
std::map<StateID, State *>::iterator i;
|
||||
i = m_stateMap.find( curID );
|
||||
|
||||
if (i == m_stateMap.end()) {
|
||||
DEBUG_LOG(("\nState %s(%d) : ", state->getName().str(), id));
|
||||
DEBUG_LOG(("Transition %d not found\n", curID));
|
||||
DEBUG_LOG(("This MUST BE FIXED!!!jba\n"));
|
||||
DEBUG_CRASH(("Invalid transition."));
|
||||
} else {
|
||||
State *st = (*i).second;
|
||||
if (st->getName().isNotEmpty()) {
|
||||
REALLY_VERBOSE_LOG(("%s') ", st->getName().str()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
REALLY_VERBOSE_LOG(("\n"));
|
||||
delete ids;
|
||||
ids = NULL;
|
||||
}
|
||||
REALLY_VERBOSE_LOG(("SM_END\n\n"));
|
||||
#endif
|
||||
#endif
|
||||
DEBUG_ASSERTCRASH(!m_locked, ("Machine is locked here, but probably should not be"));
|
||||
if (m_defaultStateInited)
|
||||
{
|
||||
DEBUG_CRASH(("you may not call initDefaultState twice for the same StateMachine"));
|
||||
return STATE_FAILURE;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_defaultStateInited = true;
|
||||
return internalSetState( m_defaultStateID );
|
||||
}
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
void StateMachine::setGoalObject( const Object *obj )
|
||||
{
|
||||
if (m_locked)
|
||||
return;
|
||||
|
||||
internalSetGoalObject( obj );
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
Bool StateMachine::isGoalObjectDestroyed() const
|
||||
{
|
||||
if (m_goalObjectID == 0)
|
||||
{
|
||||
return false; // never had a goal object
|
||||
}
|
||||
return getGoalObject() == NULL;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
void StateMachine::halt()
|
||||
{
|
||||
m_locked = true;
|
||||
m_currentState = NULL; // don't exit current state, just clear it.
|
||||
#ifdef STATE_MACHINE_DEBUG
|
||||
if (getWantsDebugOutput())
|
||||
{
|
||||
DEBUG_LOG(("%d '%s' -- '%s' %x halt()\n", TheGameLogic->getFrame(), m_owner->getTemplate()->getName().str(), m_name.str(), this));
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
void StateMachine::internalSetGoalObject( const Object *obj )
|
||||
{
|
||||
if (obj) {
|
||||
m_goalObjectID = obj->getID();
|
||||
internalSetGoalPosition(obj->getPosition());
|
||||
}
|
||||
else {
|
||||
m_goalObjectID = INVALID_ID;
|
||||
}
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
Object *StateMachine::getGoalObject()
|
||||
{
|
||||
return TheGameLogic->findObjectByID( m_goalObjectID );
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
const Object *StateMachine::getGoalObject() const
|
||||
{
|
||||
return TheGameLogic->findObjectByID( m_goalObjectID );
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
void StateMachine::setGoalPosition( const Coord3D *pos )
|
||||
{
|
||||
if (m_locked)
|
||||
return;
|
||||
|
||||
internalSetGoalPosition( pos );
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
void StateMachine::internalSetGoalPosition( const Coord3D *pos )
|
||||
{
|
||||
if (pos) {
|
||||
m_goalPosition = *pos;
|
||||
// Don't clear the goal object, or everything breaks. Like construction of buildings.
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
/** CRC */
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
void StateMachine::crc( Xfer *xfer )
|
||||
{
|
||||
|
||||
} // end crc
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
/** Xfer Method
|
||||
* Version Info
|
||||
* 1: Initial version
|
||||
*/
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
void StateMachine::xfer( Xfer *xfer )
|
||||
{
|
||||
|
||||
// version
|
||||
XferVersion currentVersion = 1;
|
||||
XferVersion version = currentVersion;
|
||||
xfer->xferVersion( &version, currentVersion );
|
||||
|
||||
xfer->xferUnsignedInt(&m_sleepTill);
|
||||
xfer->xferUnsignedInt(&m_defaultStateID);
|
||||
StateID curStateID = getCurrentStateID();
|
||||
xfer->xferUnsignedInt(&curStateID);
|
||||
if (xfer->getXferMode() == XFER_LOAD) {
|
||||
// We are going to jump into the current state. We don't call onEnter or onExit, because the
|
||||
// state was already active when we saved.
|
||||
m_currentState = internalGetState( curStateID );
|
||||
}
|
||||
|
||||
Bool snapshotAllStates = false;
|
||||
#ifdef _DEBUG
|
||||
//snapshotAllStates = true;
|
||||
#endif
|
||||
xfer->xferBool(&snapshotAllStates);
|
||||
if (snapshotAllStates) {
|
||||
std::map<StateID, State *>::iterator i;
|
||||
// count all states in the mapping
|
||||
Int count = 0;
|
||||
for( i = m_stateMap.begin(); i != m_stateMap.end(); ++i )
|
||||
count++;
|
||||
Int saveCount = count;
|
||||
xfer->xferInt(&saveCount);
|
||||
if (saveCount!=count) {
|
||||
DEBUG_CRASH(("State count mismatch - %d expected, %d read", count, saveCount));
|
||||
throw SC_INVALID_DATA;
|
||||
}
|
||||
for( i = m_stateMap.begin(); i != m_stateMap.end(); ++i ) {
|
||||
State *state = (*i).second;
|
||||
StateID id = state->getID();
|
||||
xfer->xferUnsignedInt(&id);
|
||||
if (id!=state->getID()) {
|
||||
DEBUG_CRASH(("State ID mismatch - %d expected, %d read", state->getID(), id));
|
||||
throw SC_INVALID_DATA;
|
||||
}
|
||||
|
||||
if( state == NULL )
|
||||
{
|
||||
DEBUG_ASSERTCRASH(state != NULL, ("state was NULL on xfer, trying to heal..."));
|
||||
// Hmm... too late to find out why we are getting NULL in our state, but if we let it go, we will Throw in xferSnapshot.
|
||||
state = internalGetState(m_defaultStateID);
|
||||
}
|
||||
xfer->xferSnapshot(state);
|
||||
}
|
||||
|
||||
} else {
|
||||
if( m_currentState == NULL )
|
||||
{
|
||||
DEBUG_ASSERTCRASH(m_currentState != NULL, ("currentState was NULL on xfer, trying to heal..."));
|
||||
// Hmm... too late to find out why we are getting NULL in our state, but if we let it go, we will Throw in xferSnapshot.
|
||||
m_currentState = internalGetState(m_defaultStateID);
|
||||
}
|
||||
xfer->xferSnapshot(m_currentState);
|
||||
}
|
||||
|
||||
|
||||
xfer->xferObjectID(&m_goalObjectID);
|
||||
xfer->xferCoord3D(&m_goalPosition);
|
||||
xfer->xferBool(&m_locked);
|
||||
xfer->xferBool(&m_defaultStateInited);
|
||||
} // end xfer
|
||||
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
/** Load post process */
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
void StateMachine::loadPostProcess( void )
|
||||
{
|
||||
|
||||
} // end loadPostProcess
|
||||
|
Reference in a new issue