902 lines
26 KiB
C++

/*
** 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