void CAI_BlendedMotor::MoveContinue()
{ 
	m_nPrimarySequence = GetInteriorSequence( ACT_INVALID );
	m_nGoalSequence = m_nPrimarySequence;

	Assert( m_nPrimarySequence != ACT_INVALID );

	if (m_nPrimarySequence == ACT_INVALID)
		return;

	m_flStartCycle = 0.0;

	m_iPrimaryLayer = AddLayeredSequence( m_nPrimarySequence, 0 );
	SetLayerWeight( m_iPrimaryLayer, 0.0 );
	SetLayerPlaybackRate( m_iPrimaryLayer, 0.0 );
	SetLayerNoRestore( m_iPrimaryLayer, true );
	SetLayerCycle( m_iPrimaryLayer, m_flStartCycle, m_flStartCycle );

	m_bDeceleratingToGoal = false;
}
//-----------------------------------------------------------------------------
// Purpose: 
// Input  : *actor - 
//			*parameters - 
//-----------------------------------------------------------------------------
void CBaseFlex::AddFlexGesture( CExpressionInfo *info )
{
	if ( !info )
		return;

	CChoreoEvent *event = info->m_pEvent;
	if ( !event )
		return;

	CChoreoScene *scene = info->m_pScene;
	if ( !scene )
		return;
	
	if (info->m_iLayer >= 0)
	{
		// this happens after StudioFrameAdvance()
		float duration = event->GetDuration( );
		float orig_duration = SequenceDuration( info->m_nSequence );

		// when we come by again after StudioFrameAdvance() has moved forward 0.1 seconds, what frame should we be on?
		float flCycle = GetLayerCycle( info->m_iLayer );
		float flNextCycle = event->GetShiftedTimeFromReferenceTime( (m_flAnimTime - info->m_flStartAnim + 0.1) / duration );

		// FIXME: what time should this use?
		SetLayerWeight( info->m_iLayer, event->GetIntensity( scene->GetTime() ) );

		float rate = (flNextCycle - flCycle) * orig_duration / 0.1;

		/*
		Msg( "%d : %.2f (%.2f) : %.3f %.3f : %.3f\n",
			info->m_iLayer,
			scene->GetTime(), 
			(scene->GetTime() - event->GetStartTime()) / duration,
			flCycle,
			flNextCycle,
			rate );
		*/

		SetLayerPlaybackRate( info->m_iLayer, rate );
	}
}
void CAI_BlendedMotor::MoveStart()
{ 
	if (m_nPrimarySequence == -1)
	{
		m_nPrimarySequence = GetSequence();
		m_flStartCycle = GetCycle();
		m_flCurrRate = 0.4;

		// Assert( !GetOuter()->HasMovement( m_nStartSequence ) );

		m_nSecondarySequence = -1;

		m_iPrimaryLayer = AddLayeredSequence( m_nPrimarySequence, 0 );
		SetLayerWeight( m_iPrimaryLayer, 0.0 );
		SetLayerPlaybackRate( m_iPrimaryLayer, 0.0 );
		SetLayerNoRestore( m_iPrimaryLayer, true );
		SetLayerCycle( m_iPrimaryLayer, m_flStartCycle, m_flStartCycle );

		m_flSecondaryWeight = 0.0;
	}
	else
	{
		// suspect that MoveStop() wasn't called when the previous route finished
		// Assert( 0 );
	}


	if (m_nGoalSequence == ACT_INVALID)
	{
		ResetGoalSequence();
	}

	m_vecPrevOrigin2 = GetAbsOrigin();
	m_vecPrevOrigin1 = GetAbsOrigin();

	m_bDeceleratingToGoal = false;
}
void CAI_BlendedMotor::SetMoveScriptAnim( float flNewSpeed )
{
	// don't bother if the npc is dead
	if (!GetOuter()->IsAlive())
		return;

	// insert ideal layers
	// FIXME: needs full transitions, as well as starting vs stopping sequences, leaning, etc.

	CAI_Navigator *pNavigator = GetNavigator();

	SetPlaybackRate( m_flCurrRate );
	// calc weight of idle animation layer that suppresses the run animation
	float flWeight = 0.0;
	if (GetIdealSpeed() > 0.0)
	{
		flWeight = 1.0 - (flNewSpeed / (GetIdealSpeed()  * GetPlaybackRate()));
	}
	if (flWeight < 0.0)
	{
		m_flCurrRate = flNewSpeed / GetIdealSpeed();
		m_flCurrRate = clamp( m_flCurrRate, 0.0, 1.0 );
		SetPlaybackRate( m_flCurrRate );
		flWeight = 0.0;
	}
	// Msg("weight %.3f rate %.3f\n", flWeight, m_flCurrRate );
	m_flCurrRate = min( m_flCurrRate + (1.0 - m_flCurrRate) * 0.8, 1.0 );

	if (m_nSavedGoalActivity == ACT_INVALID)
	{
		ResetGoalSequence();
	}

	// detect state change
	Activity activity = GetOuter()->NPC_TranslateActivity( m_nSavedGoalActivity );
	if ( activity != m_nSavedTranslatedGoalActivity )
	{
		m_nSavedTranslatedGoalActivity = activity;
		m_nInteriorSequence = ACT_INVALID;
		m_nGoalSequence = pNavigator->GetArrivalSequence( m_nPrimarySequence );
	}

	if (m_bDeceleratingToGoal)
	{
		// find that sequence to play when at goal
		m_nGoalSequence = pNavigator->GetArrivalSequence( m_nPrimarySequence );

		if (m_nGoalSequence == ACT_INVALID)
		{
			m_nGoalSequence = GetInteriorSequence( m_nPrimarySequence );
		}

		Assert( m_nGoalSequence != ACT_INVALID );
	}

	if (m_flSecondaryWeight == 1.0 || (m_iSecondaryLayer != -1 && m_nPrimarySequence == m_nSecondarySequence))
	{
		// secondary layer at full strength last time, delete the primary and shift down
		RemoveLayer( m_iPrimaryLayer, 0.0, 0.0 );

		m_iPrimaryLayer = m_iSecondaryLayer;
		m_nPrimarySequence = m_nSecondarySequence;
		m_iSecondaryLayer = -1;
		m_nSecondarySequence = ACT_INVALID;
		m_flSecondaryWeight = 0.0;
	}

	// look for transition sequence if needed
	if (m_nSecondarySequence == ACT_INVALID)
	{
		if (!m_bDeceleratingToGoal && m_nGoalSequence != GetInteriorSequence( m_nPrimarySequence ))
		{
			// strob interior sequence in case it changed
			m_nGoalSequence = GetInteriorSequence( m_nPrimarySequence );
		}

		if (m_nGoalSequence != ACT_INVALID && m_nPrimarySequence != m_nGoalSequence)
		{
			// Msg("From %s to %s\n", GetOuter()->GetSequenceName( m_nPrimarySequence ), GetOuter()->GetSequenceName( m_nGoalSequence ) );
			m_nSecondarySequence = GetOuter()->FindTransitionSequence(m_nPrimarySequence, m_nGoalSequence, NULL);
			if (m_nSecondarySequence == ACT_INVALID)
				m_nSecondarySequence = m_nGoalSequence;
		}
	}

	// set blending for 
	if (m_nSecondarySequence != ACT_INVALID)
	{
		if (m_iSecondaryLayer == -1)
		{
			m_iSecondaryLayer = AddLayeredSequence( m_nSecondarySequence, 0 );
			SetLayerWeight( m_iSecondaryLayer, 0.0 );
			if (m_nSecondarySequence == m_nGoalSequence)
			{
				SetLayerPlaybackRate( m_iSecondaryLayer, 0.0 );
			}
			else
			{
				SetLayerPlaybackRate( m_iSecondaryLayer, 1.0 );
			}
			SetLayerNoRestore( m_iSecondaryLayer, true );
			m_flSecondaryWeight = 0.0;
		}

		m_flSecondaryWeight = min( m_flSecondaryWeight + 0.3, 1.0 );

		if (m_flSecondaryWeight < 1.0)
		{
			SetLayerWeight( m_iPrimaryLayer, (flWeight - m_flSecondaryWeight * flWeight) / (1.0f - m_flSecondaryWeight * flWeight) );
			SetLayerWeight( m_iSecondaryLayer, flWeight * m_flSecondaryWeight );
		}
		else
		{
			SetLayerWeight( m_iPrimaryLayer, 0.0f );
			SetLayerWeight( m_iSecondaryLayer, flWeight );
		}
	}
	else
	{
		// recreate layer if missing
		if (m_iPrimaryLayer == -1)
		{
			MoveContinue();
		}

		// try to catch a stale layer
		if (m_iSecondaryLayer != -1)
		{
			// secondary layer at full strength last time, delete the primary and shift down
			RemoveLayer( m_iSecondaryLayer, 0.0, 0.0 );
			m_iSecondaryLayer = -1;
			m_nSecondarySequence = ACT_INVALID;
			m_flSecondaryWeight = 0.0;
		}

		// debounce
		// flWeight = flWeight * 0.5 + 0.5 * GetOuter()->GetLayerWeight( m_iPrimaryLayer );
		SetLayerWeight( m_iPrimaryLayer, flWeight );
	}
}
bool CAI_BlendedMotor::AddTurnGesture( float flYD )
{

	// some funky bug with human turn gestures, disable for now
	return false;

	// try using a turn gesture
	Activity activity = ACT_INVALID;
	float weight = 1.0;
	float turnCompletion = 1.0;

	if (m_flNextTurnGesture > gpGlobals->curtime)
	{
		/*
		if ( GetOuter()->m_debugOverlays & OVERLAY_NPC_SELECTED_BIT )
		{
			Msg( "%.1f : [ %.2f ]\n", flYD, m_flNextTurnAct - gpGlobals->curtime );
		}
		*/
		return false;
	}

	if ( GetOuter()->IsMoving() || GetOuter()->IsCrouching() )
	{
		return false;
	}

	if (fabs( flYD ) < 15)
	{
		return false;
	}
	else if (flYD < -45)
	{
		activity = ACT_GESTURE_TURN_RIGHT90;
		weight = flYD / -90;
		turnCompletion = 0.36;
	}
	else if (flYD < 0)
	{
		activity = ACT_GESTURE_TURN_RIGHT45;
		weight = flYD / -45;
		turnCompletion = 0.4;
	}
	else if (flYD <= 45)
	{
		activity = ACT_GESTURE_TURN_LEFT45;
		weight = flYD / 45;
		turnCompletion = 0.4;
	}
	else
	{
		activity = ACT_GESTURE_TURN_LEFT90;
		weight = flYD / 90;
		turnCompletion = 0.36;
	}

	int seq = SelectWeightedSequence( activity );

	if (scene_flatturn->GetBool() && GetOuter()->IsCurSchedule( SCHED_SCENE_GENERIC ))
	{
		Activity flatactivity = activity;

		if (activity == ACT_GESTURE_TURN_RIGHT90)
		{
			flatactivity = ACT_GESTURE_TURN_RIGHT90_FLAT;
		}
		else if (activity == ACT_GESTURE_TURN_RIGHT45)
		{
			flatactivity = ACT_GESTURE_TURN_RIGHT45_FLAT;
		}
		else if (activity == ACT_GESTURE_TURN_LEFT90)
		{
			flatactivity = ACT_GESTURE_TURN_LEFT90_FLAT;
		}
		else if (activity == ACT_GESTURE_TURN_LEFT45)
		{
			flatactivity = ACT_GESTURE_TURN_LEFT45_FLAT;
		}

		if (flatactivity != activity)
		{
			int newseq = SelectWeightedSequence( flatactivity );
			if (newseq != ACTIVITY_NOT_AVAILABLE)
			{
				seq = newseq;
			}
		}
	}

	if (seq != ACTIVITY_NOT_AVAILABLE)
	{
		int iLayer = GetOuter()->AddGestureSequence( seq );
		if (iLayer != -1)
		{
			GetOuter()->SetLayerPriority( iLayer, 100 );
			// vary the playback a bit
			SetLayerPlaybackRate( iLayer, 1.0 );
			float actualDuration = GetOuter()->GetLayerDuration( iLayer );

			float rate = enginerandom->RandomFloat( 0.5, 1.1 );
			float diff = fabs( flYD );
			float speed = (diff / (turnCompletion * actualDuration / rate)) * 0.1;

			speed = clamp( speed, 15, 35 );
			speed = min( speed, diff );

			actualDuration = (diff / (turnCompletion * speed)) * 0.1 ;

			GetOuter()->SetLayerDuration( iLayer, actualDuration );

			SetLayerWeight( iLayer, weight );

			SetYawSpeed( speed );

			Remember( bits_MEMORY_TURNING );

			// don't overlap the turn portion of the gestures, and don't play them too often
			m_flNextTurnGesture = gpGlobals->curtime + max( turnCompletion * actualDuration, 0.3 );

			/*
			if ( GetOuter()->m_debugOverlays & OVERLAY_NPC_SELECTED_BIT )
			{
				Msg( "%.1f : %.2f %.2f : %.2f (%.2f)\n", flYD, weight, speed, actualDuration, turnCompletion * actualDuration );
			}
			*/
			return true;
		}
		else
		{
			return false;
		}
	}
	return false;
}
//-----------------------------------------------------------------------------
// Purpose: Add string indexed scene/expression/duration to list of active expressions
// Input  : scenefile - 
//			expression - 
//			duration - 
//-----------------------------------------------------------------------------
void CBaseFlex::AddSceneEvent( CChoreoScene *scene, CChoreoEvent *event )
{
	if ( !scene || !event )
	{
		Msg( "CBaseFlex::AddExpression:  scene or event was NULL!!!\n" );
		return;
	}


	CExpressionInfo info;

	info.m_pEvent		= event;
	info.m_pScene		= scene;
	info.m_bStarted	= false;

	switch ( event->GetType() )
	{
	case CChoreoEvent::SEQUENCE: 
		{
			info.m_nSequence = LookupSequence( event->GetParameters() );
			info.m_iLayer = -1;

			if (info.m_nSequence >= 0)
			{
				info.m_iLayer = AddLayeredSequence( info.m_nSequence, scene->GetChannelIndex( event->GetChannel()) );
				SetLayerWeight( info.m_iLayer, 0.0 );
				info.m_flStartAnim = m_flAnimTime; // ??
			}
		}
		break;
	case CChoreoEvent::GESTURE:
		{
			info.m_nSequence = LookupSequence( event->GetParameters() );
			info.m_iLayer = -1;

			if (info.m_nSequence >= 0)
			{
				// this happens before StudioFrameAdvance()
				info.m_iLayer = AddLayeredSequence( info.m_nSequence, scene->GetChannelIndex( event->GetChannel()) );
				SetLayerDuration( info.m_iLayer, event->GetDuration() );
				SetLayerWeight( info.m_iLayer, 0.0 );

				bool looping = ((GetSequenceFlags( info.m_nSequence ) & STUDIO_LOOPING) != 0);
				if ( looping )
				{
					DevMsg( 1, "vcd error, gesture %s of model %s is marked as STUDIO_LOOPING!\n", 
						event->GetParameters(), GetModelName() );
				}

				SetLayerLooping( info.m_iLayer, false ); // force to not loop

				// figure out the animtime when this was frame 0
				float dt =  scene->GetTime() - event->GetStartTime();
				info.m_flStartAnim = m_flAnimTime - dt; // ??
				float flCycle = 0;

				// assuming anim time is going to advance 0.1 seconds, what should our new cycle be?
				float duration = event->GetDuration( );
				float orig_duration = SequenceDuration( info.m_nSequence );
				SetLayerCycle( info.m_iLayer, 0.0 );
				float flNextCycle = event->GetShiftedTimeFromReferenceTime( (m_flAnimTime - info.m_flStartAnim + 0.1) / duration );

				float rate = (flNextCycle - flCycle) * orig_duration / 0.1;
				SetLayerPlaybackRate( info.m_iLayer, rate );
			}
		}
		break;
	}

	m_Expressions.AddToTail( info );
}