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

