//////////////////////////////////////////////////////////////////////////////
//
// Asura_Dialogue_Playback.cpp
//
// Copyright (c) 2006 Rebellion. All rights reserved.
//
//////////////////////////////////////////////////////////////////////////////

// I N C L U D E S ///////////////////////////////////////////////////////////

#include "Core/Asura.h"

#ifdef ASURA_USE_DIALOGUE_SYSTEM

// This file's header.
#include "Sound/Asura_Dialogue_Playback.h"

// Asura includes
#include "Core/Asura_Timers.h"
#include "Cutscene/Asura_Cutscene_System.h"
#include "Cutscene/Asura_Cutscene.h"
#include "Cutscene/Asura_Cutscene_Actor.h"
#include "Entity/PhysicalObject/Asura_ClientEntity_PhysicalObject.h"
#include "Entity/Asura_Entity.h"
#include "Entity/Asura_StaticMessage_System.h"
#if defined(ASURA_USE_FACIAL_ANIMATION_SYSTEM)
#include "FacialAnimation/Asura_FacialAnimation_System.h"
#endif	//defined(ASURA_USE_FACIAL_ANIMATION_SYSTEM)
#include "Network/Asura_Guid.h"
#include "Network/Asura_Message.h"
#include "Network/Asura_Network.h"
#include "Sound/Asura_Audio_System.h"
#include "Sound/Asura_Audio_Voice.h"
#include "Sound/Asura_Dialogue.h"
#include "Sound/Asura_Dialogue_System.h"
#include "Sound/Asura_Sound.h"
#include "Sound/Asura_Sound_Mixer.h"

// Library includes
#include <wchar.h> // for wcslen

#ifdef ASURA_TOOLS
#include "Tools_System.h"
#include "Sound/Tools_Sound_System.h"
#endif

#ifdef ASURA_USE_ARTICULATION_SYSTEM
#include "LipSync/Asura_Lipsync_Manager.h"
#endif

const u_int uASURA_DIALOGUE_MAX_CONTROLLERS = ASURA_DIALOGUE_MAX_ACTIVE_EVENTS * 4;

// C L A S S E S /////////////////////////////////////////////////////////////
// Asura_Dialogue_Controller is the class that actually manages playback internally
class Asura_Dialogue_Controller
{
public:

	Asura_Dialogue_Controller();
	~Asura_Dialogue_Controller();

	void Initialise();
	void Shutdown();

	void Play( int iPlayableIndex, 
				const Asura_Dialogue_Event* pxEvent, 
				Asura_Dialogue_PlaybackObject* pxOwner );
	void Play( const Asura_Dialogue_Line* pxLine,
				Asura_Guid uSpeakerGuid,
				Asura_Dialogue_PlaybackObject* pxOwner);


	bool Update();	// returns whether still active

#if defined(ASURA_TOOLS)
	void DialogueEventDeleted(const Asura_Dialogue_Event* pxEventDeleted);
#endif //#if defined(ASURA_TOOLS) || defined(ASURA_USE_STREAMING)

	void SkipLine();

	// JAT 08/03/07
	void SetVolumeScale( const float fVolumeScale ) { m_fVolumeScale = fVolumeScale; }
	float GetVolumeScale( void ) const { return m_fVolumeScale; }


	const Asura_Dialogue_Event* GetEvent() const { return m_pxEvent; }
	const Asura_Dialogue_Line* GetLine() const { return m_pxDialogueLine; }

private:

	void CreateSound( const Asura_Dialogue_Line& xLine );
	void StartPlaying();		// plays sound or next controller event
	void StopPlaying();
	void UpdateWhilePlaying();
	bool IsPlaying() const;		// true iff the next controller is active or the sound is playing.

	const Asura_Entity_Client* FindEntity() const;
	const Asura_Cutscene_Actor *FindActor() const;

	bool GetSoundPosition(Asura_Vector_3& xPos) const;

	float CalculateSubtitleDuration(const Asura_Dialogue_Line* pxDialogueLine,const Asura_UnicodeChar* wszText);

	const Asura_Dialogue_Event*		m_pxEvent;				// event may be null if just playing a single dialogue line
	Asura_Dialogue_PlaybackObject*	m_pxOwner;				// needed for sample playback
	const Asura_Dialogue_Line*		m_pxDialogueLine;
	Asura_Guid						m_uCurrentSpeaker;
#ifdef ASURA_USE_ARTICULATION_SYSTEM
	int m_iArticulationIndex;
#endif
	bool						m_bActive;				// set during Update() based on sound status, play mode etc.
	float						m_fRemainingDelayTime;	// if >0 and goes to 0 during Update(), trigger the sound play.

	u_int						m_uCurrentItem;			// for "all sequential" play mode

	Asura_Hash_ID				m_uEndMessageID; //ID of static message to send when the playable ends
	
	// sound handle can only be used if next controller is NULL.
	Asura_Handle				m_iSoundSystemHandle;
	Asura_Dialogue_Controller*	m_pxNextController;

	//time left to display subtitles , for dialogue lines that have no sound.
	float						m_fSubtitleTimer;

	// JAT 08/03/07 - current volume scale
	float						m_fVolumeScale;
		
	bool						m_bEntityIsDeadOrMissing;

	bool						m_bHasAppliedMixerPreset;

	// non-copyable
	Asura_Dialogue_Controller(const Asura_Dialogue_Controller&);
	Asura_Dialogue_Controller* operator= (const Asura_Dialogue_Controller&);
};

// S T A T I C   G L O B A L S ///////////////////////////////////////////////
static Asura_Mempool g_xDialogueControllerMempool( 
	sizeof(Asura_Dialogue_Controller), uASURA_DIALOGUE_MAX_CONTROLLERS );

// S T A T I C   F U N C T I O N S ///////////////////////////////////////////
static bool AllocateController(Asura_Dialogue_Controller*& pxController)
{
	Asura_Assert(!pxController, "controller ptr not NULL, possible overwrite?");
	
	pxController = reinterpret_cast<Asura_Dialogue_Controller*>
		(g_xDialogueControllerMempool.Allocate());

	Asura_Assert(pxController, "Run out of dialogue controllers; possible leak?");

	if (pxController)
	{
		pxController->Initialise();
		return true;
	}
	return false;
}

static void DeallocateController(Asura_Dialogue_Controller*& pxController)
{
	if (pxController)
	{
		pxController->Shutdown();
		
		g_xDialogueControllerMempool.Deallocate( pxController );
		pxController = NULL;
	}
}

static bool AllocateCharacterMapping(Asura_Dialogue_CharacterMappingList*& pxMapping)
{
	Asura_Assert(!pxMapping, "mapping ptr not NULL, possible overwrite?");
	// TODO - mempool
	pxMapping = new Asura_Dialogue_CharacterMappingList;
	if (pxMapping)
	{
		return true;
	}
	return false;
}

static void DeallocateCharacterMapping(Asura_Dialogue_CharacterMappingList*& pxMapping)
{
	if (pxMapping)
	{
		// TODO - mempool
		delete pxMapping;
		pxMapping = NULL;
	}
}
// A S U R A _ D I A L O G U E _ P L A Y B A C K O B J E C T /////////////////
Asura_Dialogue_PlaybackObject::Asura_Dialogue_PlaybackObject()
: m_bNoExternalHandle(true)
, m_bPlaying(false)
, m_pxFirstController(NULL)
, m_pxCharacterMapping(NULL)
, m_bFadingOut(false)
, m_fFadeOutStartTime(0.0f)
, m_fFadeOutPeriod(0.0f)
{
	Reset();
}

Asura_Dialogue_PlaybackObject::~Asura_Dialogue_PlaybackObject()
{
	Reset();
}

void Asura_Dialogue_PlaybackObject::Update()
{
	if(m_bFadingOut)
	{
		float fFadeOutElapsedTime = Asura_Timers::GetLocalScaledTime() - m_fFadeOutStartTime;
		if(fFadeOutElapsedTime > m_fFadeOutPeriod)
		{
			Stop();
		}
		else
		{
			SetVolumeScale(1.0f - (fFadeOutElapsedTime / m_fFadeOutPeriod));
		}
	}

	m_bPlaying = false;
	if (m_pxFirstController)
	{
		m_bPlaying = m_pxFirstController->Update();
	}

	if (!m_bPlaying)
	{
		//notify the dialogue instance that this has finished. RAR 24/11/2006
		if(m_uInstanceGuid != ASURA_GUID_UNREGISTERED && Asura_Network::IsServer())
		{
			Asura_Message* pxMessage = Asura_Network::CreateMessage(ASURA_MSG_NOTIFYFINISHED, sizeof(Asura_Message_Dialogue_NotifyFinished));
			pxMessage->To =	m_uInstanceGuid;
			pxMessage->From = ASURA_GUID_UNREGISTERED;

			Asura_Message_Dialogue_NotifyFinished* pxData = reinterpret_cast<Asura_Message_Dialogue_NotifyFinished*>( pxMessage->GetData() );
			pxData->m_iMessageData = m_iNotifyFinishedMessageData;

			Asura_Network::Send(pxMessage);
			
		}
		
		Reset();
	}
}

DEBUGVAR bool dbg_bLogDialoguePlayback = false;

void Asura_Dialogue_PlaybackObject::Play(const Asura_Dialogue_Event* pxEvent, 
										 const Asura_Dialogue_CharacterMappingList* pxMappings,
										 Asura_Guid uSpeakerGuid,
										 const Asura_Vector_3* pxPosition,
										 unsigned short usMessageBlockFromInstance,
										 Asura_Guid uInstanceGuid,
										 Asura_Hash_ID uInstanceHandlerID)
{
	if (dbg_bLogDialoguePlayback)
	{
		Asura_OutputToDebugger(("PlayDialogue 0x%x (%s)\n", pxEvent->GetHashID(), pxEvent->GetName()));
	}

	m_uInstanceGuid = uInstanceGuid;
	m_uInstanceHandlerID = uInstanceHandlerID;
	m_usMessageBlockFromInstance = usMessageBlockFromInstance;
	m_uSpeakerGuid = uSpeakerGuid;

	Asura_Assert(pxMappings || (uSpeakerGuid != ASURA_GUID_UNREGISTERED) || pxPosition, "no character mapping");
	if (pxMappings)
	{
		// deep copy of mappings.
		AllocateCharacterMapping(m_pxCharacterMapping);
		if (m_pxCharacterMapping)
		{
			u_int uNumMappings = pxMappings->m_xMappings.Size();
			m_pxCharacterMapping->m_xMappings.SetSize(uNumMappings);
			for (u_int u = 0; u < uNumMappings; ++u)
			{
				m_pxCharacterMapping->m_xMappings[u] = pxMappings->m_xMappings[u];
			}
		}
	}
	if(pxPosition)
	{
		m_xPositionOverride = *pxPosition;
		m_bUsePositionOverride = true;
	}

	Asura_Assert(!m_bPlaying, "already playing");

	m_bPlaying = true;

	if (AllocateController(m_pxFirstController))
	{
		//trigger start of event messages. RAR 21/11/2006
		Asura_Dialogue_System::QueueStaticMessage(pxEvent,pxEvent->GetInitialMessageID(),ASURA_GUID_UNREGISTERED,this);
		
		m_pxFirstController->Play( pxEvent->GetPlayableIndex(), pxEvent, this );
	}
}

void Asura_Dialogue_PlaybackObject::Play(const Asura_Dialogue_Line* pxLine, 
										 Asura_Guid uSpeakerGuid,
										 Asura_Guid uInstanceGuid /*= ASURA_GUID_UNREGISTERED*/,
										 int iNotifyFinishedMessageData /*=0*/)
{
	Asura_Assert(!m_bPlaying, "already playing");

	m_bPlaying = true;

	m_uInstanceGuid = uInstanceGuid;
	m_iNotifyFinishedMessageData = 0;

	if (AllocateController(m_pxFirstController))
	{
		m_pxFirstController->Play( pxLine, uSpeakerGuid, this );
	}
}


void Asura_Dialogue_PlaybackObject::Stop()
{
	Reset();
}

void Asura_Dialogue_PlaybackObject::SetVolumeScale( const float fVolumeScale )
{
	Asura_Assert( m_bPlaying, "not playing" );
	Asura_Assert( m_pxFirstController, "" );
	m_pxFirstController->SetVolumeScale( fVolumeScale );
}

float Asura_Dialogue_PlaybackObject::GetVolumeScale( void ) const
{
	Asura_Assert( m_bPlaying, "not playing" );
	Asura_Assert( m_pxFirstController, "" );
	return m_pxFirstController->GetVolumeScale();
}

#if defined(ASURA_TOOLS)
void Asura_Dialogue_PlaybackObject::DialogueEventDeleted(const Asura_Dialogue_Event* pxEventDeleted)
{
	if(m_pxFirstController) m_pxFirstController->DialogueEventDeleted(pxEventDeleted);
}
#endif

// FindGuidFromCharacter() looks up the character in the mapping and returns the guid.
Asura_Guid Asura_Dialogue_PlaybackObject::FindGuidFromCharacter( Asura_Hash_ID uCharacterHashID ) const
{
	if(!m_pxCharacterMapping)
	{
		//If there isn't a character mapping , just use the same guid for all lines.
		Asura_Assert(m_uSpeakerGuid != ASURA_GUID_UNREGISTERED || m_bUsePositionOverride,"Expecting a valid speaker guid if there isn't a character mapping");
		return m_uSpeakerGuid;
	}
	
	const Asura_HeapArray<Asura_Dialogue_CharacterMapping>& xMappings = m_pxCharacterMapping->m_xMappings;
	u_int uNumMappings = xMappings.Size();
	for (u_int u = 0; u < uNumMappings; ++u)
	{
		const Asura_Dialogue_CharacterMapping& xCurMapping = xMappings[u];
		if (xCurMapping.m_uCharacterHashID == uCharacterHashID)
		{
			return xCurMapping.m_uCharacterGuid;
		}
	}
	#ifdef ASURA_TOOLS
	//Typically a character mapping won't be available when previewing in tools. RAR 21/11/2006
	if(!Tools_System::IsActive())
	#endif
	{
		Asura_Assert(false, "failed to find character mapping for dialogue");
	}
	return ASURA_GUID_UNREGISTERED;
}

void Asura_Dialogue_PlaybackObject::Reset()
{
	m_bPlaying = false;
	m_uInstanceGuid = ASURA_GUID_UNREGISTERED;
	m_iNotifyFinishedMessageData = 0;
	m_uInstanceHandlerID = 0;
	m_usMessageBlockFromInstance = ASURA_STATICMESSAGE_EMPTYBLOCK;
	m_uSpeakerGuid = ASURA_GUID_UNREGISTERED;
	m_bUsePositionOverride = false;
	m_xPositionOverride.Zero();
	m_bFadingOut = false;
	m_fFadeOutStartTime = 0.0f;
	m_fFadeOutPeriod = 0.0f;

	if (m_pxFirstController)
	{
		DeallocateController(m_pxFirstController);
		Asura_Assert(!m_pxFirstController, "DeallocateController should NULLify ptr");
	}

	if (m_pxCharacterMapping)
	{
		DeallocateCharacterMapping(m_pxCharacterMapping);
		Asura_Assert(!m_pxCharacterMapping, "DeallocateCharacterMapping should NULLify ptr");
	}

}

void Asura_Dialogue_PlaybackObject::SkipLine()
{
	if(m_pxFirstController)
	{
		m_pxFirstController->SkipLine();
	}
}

void Asura_Dialogue_PlaybackObject::SetPositionOverride( const Asura_Vector_3& xPosition)
{
	m_bUsePositionOverride = true;
	m_xPositionOverride = xPosition;
}

const Asura_Dialogue_Event* Asura_Dialogue_PlaybackObject::GetEvent() const
{
	return m_pxFirstController ? m_pxFirstController->GetEvent() : NULL;
}

const Asura_Dialogue_Line* Asura_Dialogue_PlaybackObject::GetLine() const
{
	return m_pxFirstController ? m_pxFirstController->GetLine() : NULL;
}

const Asura_Dialogue_Playable* Asura_Dialogue_PlaybackObject::GetEventOrLine() const
{
	if (m_pxFirstController)
	{
		const Asura_Dialogue_Event* pxEvent = m_pxFirstController->GetEvent();
		if (!pxEvent)
		{
			return m_pxFirstController->GetLine();
		}
		return pxEvent;
	}
	return NULL;
}

void Asura_Dialogue_PlaybackObject::FadeOut(float fPeriod)
{
	Asura_Assert(fPeriod > 0.0f, "fPeriod should not be less than zero!");
	if(fPeriod > 0.0f)
	{
		m_bFadingOut = true;
		m_fFadeOutStartTime = Asura_Timers::GetLocalScaledTime();
		m_fFadeOutPeriod = fPeriod;
	}
	else
	{
		Stop();
	}
}


// A S U R A _ S O U N D _ E V E N T _ C O N T R O L L E R ///////////////////
Asura_Dialogue_Controller::Asura_Dialogue_Controller()
{
	Initialise();
}

Asura_Dialogue_Controller::~Asura_Dialogue_Controller()
{
	Shutdown();
}

void Asura_Dialogue_Controller::Initialise()
{
	m_pxEvent = NULL;
	m_pxOwner = NULL;
	m_pxDialogueLine = NULL;

	m_uCurrentSpeaker = ASURA_GUID_UNREGISTERED;

	m_bActive = false;
	m_fRemainingDelayTime = 0.0f;

	m_uCurrentItem = 0;
	m_uEndMessageID = 0;

	// sound handle can only be used if next controllers are NULL.
	m_iSoundSystemHandle = ASURA_INVALID_HANDLE;
	m_pxNextController = NULL;

	m_fSubtitleTimer = 0.0f;

	// JAT 08/0/07
	m_fVolumeScale = 1.0f;

#ifdef ASURA_USE_ARTICULATION_SYSTEM
	m_iArticulationIndex = -1;
#endif
	m_bEntityIsDeadOrMissing = false;

	m_bHasAppliedMixerPreset = false;
}

void Asura_Dialogue_Controller::Shutdown()
{
	StopPlaying();
}

void Asura_Dialogue_Controller::Play( int iPlayableIndex, 
										const Asura_Dialogue_Event* pxEvent, 
										Asura_Dialogue_PlaybackObject* pxOwner )
{
	if (iPlayableIndex < 0)
	{
		return;
	}

	const Asura_Dialogue_Playable* pxPlayable = pxEvent->GetPlayable(iPlayableIndex);
	m_uEndMessageID = pxEvent->GetEndMessageID(iPlayableIndex);
	Asura_Hash_ID uStartMessageID = pxEvent->GetStartMessageID(iPlayableIndex);

	m_pxEvent = pxEvent;
	m_pxOwner = pxOwner;
	m_bEntityIsDeadOrMissing = false;


	// JAT 08/03/07 - default volume scale to 1.0
	m_fVolumeScale = 1.0f;

	if (pxPlayable->IsEvent())
	{
		const Asura_Dialogue_Event* pxChildEvent = static_cast<const Asura_Dialogue_Event*>(pxPlayable);

		if(pxChildEvent->CheckPlayProbability())
		{
			if (AllocateController(m_pxNextController))
			{
				//trigger message for the start of the playable
	 			if(uStartMessageID) Asura_Dialogue_System::QueueStaticMessage(m_pxEvent,uStartMessageID,m_uCurrentSpeaker,pxOwner);
				
				//trigger start of event messages for the child event. RAR 21/11/2006
				Asura_Dialogue_System::QueueStaticMessage(pxChildEvent,pxChildEvent->GetInitialMessageID(),ASURA_GUID_UNREGISTERED,pxOwner);

				// recursive play
				m_pxNextController->Play( pxChildEvent->GetPlayableIndex(), pxChildEvent, pxOwner );
			}
		}
		else
		{
			//Decided not to play the child event after all ,so probably don't want to trigger any messages
			//from ending it. RAR 22/03/2007
			m_uEndMessageID = 0;
		}
	}
	else
	{
		Asura_Assert(pxPlayable->IsLine(), "must be a dialogue line if not an event"); 
		const Asura_Dialogue_Line* pxLine = static_cast<const Asura_Dialogue_Line*>(pxPlayable);

		// set the current speaker
		m_uCurrentSpeaker = pxOwner->FindGuidFromCharacter( pxLine->GetCharacter() );

	 	//trigger message for the start of the playable
 		if(uStartMessageID) Asura_Dialogue_System::QueueStaticMessage(m_pxEvent,uStartMessageID,m_uCurrentSpeaker,pxOwner);

		//play any animation for this line
		if(m_uCurrentSpeaker != ASURA_GUID_UNREGISTERED && pxLine->GetAnimation())
		{
			Asura_Dialogue_System::TriggerAnimation(m_uCurrentSpeaker,pxLine->GetAnimation());
		}

		m_pxDialogueLine = pxLine;

		CreateSound(*pxLine);
	}

	m_bActive = true;
}

void Asura_Dialogue_Controller::Play( const Asura_Dialogue_Line* pxLine,
										Asura_Guid uSpeakerGuid, 
										Asura_Dialogue_PlaybackObject* pxOwner )
{
	if (!pxLine)
	{
		return;
	}
	m_pxOwner = pxOwner;
	
	// set the current speaker
	m_uCurrentSpeaker = uSpeakerGuid;

	m_pxDialogueLine = pxLine;

	// JAT 08/03/07 - default volume scale to 1.0
	m_fVolumeScale = 1.0f;

	CreateSound(*pxLine);

	m_bActive = true;
}



bool Asura_Dialogue_Controller::Update()
{
	// recursive update
	if (m_pxNextController)
	{
		m_pxNextController->Update();
	}

	if (m_bActive)
	{
		if (IsPlaying())
		{
			UpdateWhilePlaying();
		}
		else
		{
			// not playing, are we waiting to start?
			if (m_fRemainingDelayTime && !m_bEntityIsDeadOrMissing)
			{
				m_fRemainingDelayTime -= Asura_Timers::GetGameTimeSlice();
				if (m_fRemainingDelayTime <= 0.0f)
				{
					m_fRemainingDelayTime = 0.0f;
					StartPlaying();
				}
			}
			else
			{
				StopPlaying();

				if(m_pxEvent)
				{
					//trigger any messages for finishing this playable. RAR 21/11/2006
					if(m_uEndMessageID) Asura_Dialogue_System::QueueStaticMessage(m_pxEvent,m_uEndMessageID,m_uCurrentSpeaker,m_pxOwner);

				
					// the sound or event has stopped, depending on play mode we're done, or we need to start another one.
					if (ASURA_DPT_ALLSEQUENTIAL == m_pxEvent->GetPlaybackType())
					{
						u_int uNextItem = m_uCurrentItem + 1;
						if (uNextItem < m_pxEvent->GetNumPlayables())
						{
							m_uCurrentItem = uNextItem;

							Play( uNextItem, m_pxEvent, m_pxOwner );
						}
						else
						{
							m_bActive = false;
						}
					}
					else
					{
						m_bActive = false;
					}
					if(!m_bActive)
					{
						//event has now finished , so trigger any end of event messages
						Asura_Dialogue_System::QueueStaticMessage(m_pxEvent,m_pxEvent->GetFinalMessageID(),ASURA_GUID_UNREGISTERED,m_pxOwner);
					}
				}
				else
				{
					//Was playing a dialogue line on its own , so finished now. RAR 28/11/2006
					m_bActive = false;
				}
			}
		}
	}// end if (m_bActive)

	return m_bActive;
}

bool Asura_Dialogue_Controller::IsPlaying() const
{
	if (m_pxNextController)
	{
		return m_pxNextController->m_bActive;
	}
	else
	{
		if(m_bEntityIsDeadOrMissing)
		{
			return false;
		}

		#if defined ASURA_USE_AUDIO_SYSTEM
			const Asura_Audio_Voice* pxVoice = Asura_Audio_System::GetVoice(m_iSoundSystemHandle);
			const bool bPlaying = (pxVoice ? pxVoice->IsPlaying() : false);
		#else
			const bool bPlaying = Asura_Sound_System::SoundIsPlaying(m_iSoundSystemHandle);
		#endif

		return (m_fSubtitleTimer > 0.0f) || bPlaying;
	}
}

void Asura_Dialogue_Controller::StartPlaying()
{
	bool bFailedToPlaySound = false;
	if (m_iSoundSystemHandle != ASURA_INVALID_HANDLE)
	{
		#if defined ASURA_USE_AUDIO_SYSTEM
			Asura_Audio_Voice* const pxVoice = Asura_Audio_System::GetVoice(m_iSoundSystemHandle);
			bFailedToPlaySound = !pxVoice;

			if(pxVoice)
			{
				pxVoice->Start();
			}
		#else
			bool bLooping = false;
			bFailedToPlaySound = !Asura_Sound_System::PlaySound(m_iSoundSystemHandle, bLooping);
		#endif
	}

	if(!bFailedToPlaySound)
	{
		//start subtitle
		if(m_pxDialogueLine)
		{
			//Check whether subtitles should be used
			const Asura_Dialogue_Line_Template* pxTemplate = m_pxDialogueLine->GetTemplate();
			bool bShowSubtitle = !(pxTemplate && pxTemplate->m_bNoSubtitle);
			
			if(bShowSubtitle && Asura_Dialogue_System::GetShowSubtitles())
			{
				const Asura_UnicodeChar* wszText = m_pxDialogueLine->GetText();
				if(wszText && *wszText)
				{
					float fDuration = CalculateSubtitleDuration(m_pxDialogueLine,wszText);

					Asura_Dialogue_System::SetCurrentDialogueLine(m_pxDialogueLine, m_uCurrentSpeaker, fDuration);

					if(m_iSoundSystemHandle == ASURA_INVALID_HANDLE)
					{
						m_fSubtitleTimer = fDuration;
					}
				}
			}

			Asura_Assert(!m_bHasAppliedMixerPreset, "Controller has already applied a mixer preset");
			Asura_Hash_ID uMixerPreset = pxTemplate ? pxTemplate->m_uMixerPreset : ASURA_HASH_ID_UNSET;
			if ((uMixerPreset != ASURA_HASH_ID_UNSET) && (!m_bHasAppliedMixerPreset))
			{
				m_bHasAppliedMixerPreset = Asura_Sound_Mixer_System::QueuePreset( uMixerPreset );
			}

#ifdef ASURA_USE_ARTICULATION_SYSTEM
			//start lipsync
			if(m_pxDialogueLine->HasArticulation())
			{
				Asura_Assert(m_iArticulationIndex == -1, "Leaked articulation index");
				m_iArticulationIndex = m_pxDialogueLine->StartArticulation(m_iSoundSystemHandle, m_uCurrentSpeaker);
			}
#endif

#if defined(ASURA_USE_FACIAL_ANIMATION_SYSTEM)
			if(m_uCurrentSpeaker != ASURA_GUID_UNREGISTERED)
			{
				Asura_FacialAnimation_AnimationHandler*	pxAnimationHandler	= Asura_FacialAnimation_System::FindAnimationHandler(m_uCurrentSpeaker);

				if(pxAnimationHandler != NULL)
				{
					pxAnimationHandler->PlayFaceAnimation(*m_pxDialogueLine, m_iSoundSystemHandle);
				}
			}
#endif	//defined(ASURA_USE_FACIAL_ANIMATION_SYSTEM)
		}
	}
}

void Asura_Dialogue_Controller::StopPlaying()
{
	if (m_pxNextController)
	{
		DeallocateController(m_pxNextController);
	}

	if (m_iSoundSystemHandle != ASURA_INVALID_HANDLE)
	{
		#if defined ASURA_USE_AUDIO_SYSTEM
			Asura_Audio_System::DestroyVoice(m_iSoundSystemHandle);
		#else
			Asura_Sound_System::DestroySound(m_iSoundSystemHandle);
		#endif
	}

	if(m_pxDialogueLine)
	{
		//If an animation was being played for this line , then stop it
		if(m_uCurrentSpeaker != ASURA_GUID_UNREGISTERED && m_pxDialogueLine->GetAnimation())
		{
			Asura_Dialogue_System::TriggerAnimation(m_uCurrentSpeaker,0);
		}

		Asura_Dialogue_System::RemoveCurrentDialogueLine(m_pxDialogueLine);

		if (m_bHasAppliedMixerPreset && m_pxDialogueLine->GetTemplate())
		{
			Asura_Hash_ID uMixerPreset = m_pxDialogueLine->GetTemplate()->m_uMixerPreset;
			Asura_Assert(uMixerPreset != ASURA_HASH_ID_UNSET,"Should be impossible have queued this preset");
			if (uMixerPreset != ASURA_HASH_ID_UNSET)
			{
				Asura_Sound_Mixer_System::UnqueuePreset( uMixerPreset );
				m_bHasAppliedMixerPreset = false; //The error conditions of Unqueue are irrelevant
			}
		}

#ifdef ASURA_USE_ARTICULATION_SYSTEM
		//stop lipsync
		if(m_pxDialogueLine->HasArticulation())
		{
			m_pxDialogueLine->StopArticulation(m_iArticulationIndex);
			m_iArticulationIndex = -1;
		}
#endif
	}
	m_fSubtitleTimer = 0.0f;

	m_bActive = false;

	// JAT 08/02/07 - reset the volume scale
	m_fVolumeScale = 1.0f;
}

void Asura_Dialogue_Controller::UpdateWhilePlaying()
{
	Asura_Assert(m_pxOwner, "NULL pointer");
	if (m_iSoundSystemHandle != ASURA_INVALID_HANDLE)
	{
		Asura_Assert(m_pxDialogueLine, "no dialogue line");
		const Asura_Dialogue_Line_Template* pxTemplate = m_pxDialogueLine->GetTemplate();
		Asura_Assert(pxTemplate, "no dialogue template");

		float fVolume = m_fVolumeScale;
		if(pxTemplate)
		{
			float fMixerVolumeScale = Asura_Sound_Mixer_System::GetMixerVolume(pxTemplate->GetMixerChannel());
			fVolume *= pxTemplate->m_fGain * fMixerVolumeScale;
		}

		#if defined ASURA_USE_AUDIO_SYSTEM
			Asura_Audio_Voice* const pxVoice = Asura_Audio_System::GetVoice(m_iSoundSystemHandle);
			if(pxVoice)
			{
				pxVoice->SetVolume(fVolume);
			}
		#else
			Asura_Sound_System::SetSoundVolume(m_iSoundSystemHandle, fVolume);
		#endif

		// update position
		if (pxTemplate && !pxTemplate->m_bVoiceover)
		{
			Asura_Vector_3 xPosition;

			if (GetSoundPosition(xPosition))
			{
				#if defined ASURA_USE_AUDIO_SYSTEM
					pxVoice->SetPos(xPosition);
				#else
					Asura_Sound_System::SetSoundPosition(m_iSoundSystemHandle, xPosition);
				#endif
			}
			else
			{
				//Asura_Assertf(false, ("Could not find speaker (%u) for dialogue controller to update the position!", m_uCurrentSpeaker) );

				//Entity is no longer with us. Flag as dead so that the dialogue will be stopped
				//Don't do this while in tools though , since the entity won't be available in general
				//while being previewed.
				//RAR 04/09/2007
				#ifdef ASURA_TOOLS
				if(!Tools_System::IsActive())
				#endif
				{
					m_bEntityIsDeadOrMissing = true;
				}
			}
		}
	}
	m_fSubtitleTimer -= Asura_Timers::GetGameTimeSlice();
}

void Asura_Dialogue_Controller::CreateSound(const Asura_Dialogue_Line& xLine)
{
	Asura_Assert(m_pxOwner, "NULL pointer");
	Asura_Assert(m_iSoundSystemHandle == ASURA_INVALID_HANDLE, "handle already set");
	const Asura_Dialogue_Line_Template* pxTemplate = xLine.GetTemplate();
	Asura_Assert(pxTemplate, "no dialogue template");

	if (!Asura_Dialogue_System::GetProjectVoiceCreateEvent() || (m_iSoundSystemHandle = Asura_Dialogue_System::GetProjectVoiceCreateEvent()(xLine, m_uCurrentSpeaker, m_fVolumeScale)) == ASURA_INVALID_HANDLE)
	{
#ifdef ASURA_USE_AUDIO_SYSTEM
		const bool bStreamed = (!pxTemplate || pxTemplate->m_bStreamed);

		Asura_Hash_ID uWave = xLine.GetSoundHash();

#ifdef ASURA_TOOLS
		if(bStreamed)
		{
			if(!Asura_Audio_System::GetStreamInfoTree().GetDataByHash(uWave))
			{
				const char* const szFilename = xLine.GetSoundFilename();

				Asura_Audio_StreamInfo* const pxInfo =
					Asura_Audio_System::AddStreamInfo(szFilename,szFilename,0,false);

				if(pxInfo)
				{
					// Ensure this won't get exported with normal streaming sounds
					pxInfo->m_bDialogue = true;
				}
			}
		}
		else
		{
			Asura_Audio_System::LoadWave(xLine.GetSoundFilename(),false);
		}
#endif
#else
		Asura_Sound_ID eSoundID = (Asura_Sound_ID)xLine.GetSoundID();
#endif

#ifdef ASURA_TOOLS
		if(Tools_Sound_System::IsDontLoadWavs())
		{
#ifdef ASURA_USE_AUDIO_SYSTEM
			uWave = ASURA_HASH_ID_UNSET;
#else
			eSoundID = ASURA_SID_NO_SOUND;
#endif
		}
#endif

		if(m_pxEvent)
		{
			m_fRemainingDelayTime = m_pxEvent->GetDelay(m_uCurrentItem);
		}

		Asura_Vector_3 xPosition;
		bool bHavePosition = GetSoundPosition(xPosition);
		if (pxTemplate && !pxTemplate->m_bVoiceover)
		{
#ifdef ASURA_TOOLS
			//Typically a client entity won't be available when previewing in tools. RAR 21/11/2006
			if(!Tools_System::IsActive())
#endif
			{
				if(!bHavePosition)
				{
					//Dead or missing entity , so don't bother playing this
					m_bEntityIsDeadOrMissing = true;
					return;
				}
			}

		}

#if defined(ASURA_USE_AUDIO_SYSTEM)

		bool bUseOcclusion = false;
		bool bUseDistanceFiltering = bHavePosition;

		if(pxTemplate)
		{
			if( pxTemplate->m_bVoiceover )
			{
				bUseOcclusion = false;
				bUseDistanceFiltering = false;
			}
			else
			{
				bUseOcclusion			= Asura_Sound_Mixer_System::GetMixerUseOcclusion		( pxTemplate->GetMixerChannel() ) && bHavePosition;
				bUseDistanceFiltering	= Asura_Sound_Mixer_System::GetMixerUseDistanceFilter	( pxTemplate->GetMixerChannel() ) && bHavePosition;
			}
		}


		// we need to switch on voice filtering for occlusion OR distance filtering
		const bool bUseVoiceFilter = bUseOcclusion || bUseDistanceFiltering;

		// Work out which voice DSP we should apply
		Asura_Hash_ID uDSPChain = ASURA_HASH_ID_UNSET;

		if( pxTemplate )
		{
			uDSPChain = pxTemplate->m_uDSPChain;
		}

		m_iSoundSystemHandle = bStreamed
			? Asura_Audio_System::CreateStreamingVoice( uWave, bUseVoiceFilter, false, 0.0f, uDSPChain )
			: Asura_Audio_System::CreateVoice( uWave, bUseVoiceFilter, false, uDSPChain);

#else
		m_iSoundSystemHandle = Asura_Sound_System::CreateSound(eSoundID);
#endif

		if (m_iSoundSystemHandle != ASURA_INVALID_HANDLE)
		{
#if defined ASURA_USE_AUDIO_SYSTEM
			Asura_Audio_Voice* const pxVoice = Asura_Audio_System::GetVoice(m_iSoundSystemHandle);
#endif

			float fVolume = m_fVolumeScale;

			if(pxTemplate)
			{
				float fMixerVolumeScale = Asura_Sound_Mixer_System::GetMixerVolume(pxTemplate->GetMixerChannel());
				fVolume = pxTemplate->m_fGain * fMixerVolumeScale;

				// AUDIOTODO: support all these properties
#if defined ASURA_USE_AUDIO_SYSTEM

				if(pxVoice)
				{
					if(pxTemplate->GetMixerChannel())
					{
						pxVoice->SetMixerChannel(pxTemplate->GetMixerChannel()->GetHashID());
					}

					const bool bApplyReverb = Asura_Sound_Mixer_System::GetMixerUseReverb(pxTemplate->GetMixerChannel());
					pxVoice->SetUsingReverb(bApplyReverb);

					const bool bApplyDSP = Asura_Sound_Mixer_System::GetMixerUseMasterDSP(pxTemplate->GetMixerChannel());
					pxVoice->SetUsingMasterDSP(bApplyDSP);

					pxVoice->SetUsingOcclusion( bUseOcclusion );

					pxVoice->SetUsingDistanceFiltering( bUseDistanceFiltering );

					const bool bIgnoreGameSpeedPitch = Asura_Sound_Mixer_System::IsIgnoreGameSpeedPitch( pxTemplate->GetMixerChannel() );
					pxVoice->SetPitchUsesTimescale( !bIgnoreGameSpeedPitch );
				}

#else

				// JAT 27/02/07 enable/disable game speed pitch scaling based on the flag in the mixer 
				const bool bIgnoreGameSpeedPitch = Asura_Sound_Mixer_System::IsIgnoreGameSpeedPitch( pxTemplate->GetMixerChannel() );
				if ( bIgnoreGameSpeedPitch == true )
				{
					Asura_Sound_System::SetSoundPitchUsesTimeScale( m_iSoundSystemHandle, !bIgnoreGameSpeedPitch );
				}

				// Reverb & distance filtering default to on during Asura_Sound_System::CreateSound()
				const bool bApplyReverb = Asura_Sound_Mixer_System::GetMixerUseReverb( pxTemplate->GetMixerChannel() );
				if ( bApplyReverb == false )
				{
					Asura_Sound_System::SetSoundUsesReverb( m_iSoundSystemHandle, bApplyReverb );
				}

				// Reverb & distance filtering default to on during Asura_Sound_System::CreateSound()
				const bool bDistanceFilter = Asura_Sound_Mixer_System::GetMixerUseDistanceFilter( pxTemplate->GetMixerChannel() );
				if ( bDistanceFilter == false )
				{
					Asura_Sound_System::SetSoundUsesDistanceFilter( m_iSoundSystemHandle, bDistanceFilter );
				}

				const bool bDoppler = Asura_Sound_Mixer_System::GetMixerUseDoppler( pxTemplate->GetMixerChannel() );
				if ( bDoppler == false )
				{
					Asura_Sound_System::SetSoundUsesDoppler( m_iSoundSystemHandle, bDoppler );
				}

				const bool bOcclusion = Asura_Sound_Mixer_System::GetMixerUseOcclusion( pxTemplate->GetMixerChannel() );
				if ( bOcclusion == false )
				{
					Asura_Sound_System::SetSoundUsesOcclusion( m_iSoundSystemHandle, bOcclusion );
				}

#endif // !defined ASURA_USE_AUDIO_SYSTEM
			}

#if defined ASURA_USE_AUDIO_SYSTEM

			if (pxVoice)
			{
				pxVoice->SetVolume(fVolume);

				if (pxTemplate && !pxTemplate->m_bVoiceover && bHavePosition)
				{
					pxVoice->SetSpeakerMappingType(ASURA_ASMT_SPACIAL);
					pxVoice->SetHasPosition(true);
					pxVoice->SetPos(xPosition);
					pxVoice->SetRadii(pxTemplate->m_fInnerRange, pxTemplate->m_fOuterRange);
				}
			}

#else

			Asura_Sound_System::SetSoundVolume(m_iSoundSystemHandle, fVolume);

			if (pxTemplate && !pxTemplate->m_bVoiceover && bHavePosition)
			{
				Asura_Sound_System::SetSoundPosition(m_iSoundSystemHandle, xPosition);
				Asura_Sound_System::SetSoundRadius(m_iSoundSystemHandle, pxTemplate->m_fInnerRange, pxTemplate->m_fOuterRange);
			}

#endif

		}
	} else {

		if(m_pxEvent)
		{
			m_fRemainingDelayTime = m_pxEvent->GetDelay(m_uCurrentItem);
		}

		Asura_Vector_3 xPosition;
		bool bHavePosition = GetSoundPosition(xPosition);
		if (pxTemplate && !pxTemplate->m_bVoiceover)
		{
#ifdef ASURA_TOOLS
			//Typically a client entity won't be available when previewing in tools. RAR 21/11/2006
			if(!Tools_System::IsActive())
#endif
			{
				if(!bHavePosition)
				{
					//Dead or missing entity , so don't bother playing this
					m_bEntityIsDeadOrMissing = true;
					return;
				}
			}

		}
	}



	if (m_fRemainingDelayTime == 0.0f)
	{
		StartPlaying();
	}
}


#if defined(ASURA_TOOLS)
void Asura_Dialogue_Controller::DialogueEventDeleted(const Asura_Dialogue_Event* pxEventDeleted)
{
	if (m_pxNextController)
	{
		m_pxNextController->DialogueEventDeleted(pxEventDeleted);
	}

	if (pxEventDeleted == m_pxEvent)
	{
		StopPlaying();
	}
}
#endif


float Asura_Dialogue_Controller::CalculateSubtitleDuration(const Asura_Dialogue_Line* pxDialogueLine,const Asura_UnicodeChar* wszText)
{
	Asura_Assert(wszText,"");
	
	#ifdef ASURA_TOOLS
	//get the length of wav file (if any) from the sound system

	#ifdef ASURA_USE_AUDIO_SYSTEM
		Asura_Hash_ID uWave = pxDialogueLine->GetSoundHash();
		const bool bGotSound = (uWave != ASURA_HASH_ID_UNSET);
	#else
		Asura_Sound_ID eSoundID = (Asura_Sound_ID)pxDialogueLine->GetSoundID();
		const bool bGotSound = (eSoundID != ASURA_SID_NO_SOUND);
	#endif

	if(bGotSound)
	{
		float fLength = 0.0f;
		if(Tools_Sound_System::IsDontLoadWavs())
		{
			//If not loading wav files then the length stored in the sound system will just be the length of a short
			//blank sound sample. 
			//Check the wav file directly in this case.
			//RAR 23/07/2007
			#ifdef ASURA_USE_AUDIO_SYSTEM
				const char* szFilename = Asura_Audio_System::GetWaveFilename(uWave);
			#else
				const char* szFilename = Asura_Sound_System::GetFileNameFromSoundID(eSoundID);
			#endif

			if(szFilename)
			{
				fLength = Tools_Sound_System::GetSoundLength(szFilename);	
			}
		}
		else
		{
			#ifdef ASURA_USE_AUDIO_SYSTEM
				// AUDIOTODO: Need to fetch wave length here
				fLength = Tools_Sound_System::GetSoundLength(uWave);
			#else
				fLength = Asura_Sound_System::GetSoundLength(eSoundID);
			#endif
		}
		if(fLength) return fLength;
	}
	#else
	//Use the stored sound length if its available
	if(pxDialogueLine->GetSoundLength()) return pxDialogueLine->GetSoundLength();
	#endif
	
	//Determine in a a duration based on the string.
	//Will most likely require further tweakage.
	int iLength = wcslen(wszText);
	return Asura_Maths::Max(1.0f,iLength/12.0f);
}

void Asura_Dialogue_Controller::SkipLine()
{
	if(m_pxNextController) m_pxNextController->SkipLine();

	//Stop this controller if it has an active sound , or a subtitle timer going.
	//This won't (I think) stop controllers that have a start delay time , but SkipLine()
	//probably shound't be skipping lines that have yet to start.
	//RAR 18/12/2006

	#if defined ASURA_USE_AUDIO_SYSTEM
		const Asura_Audio_Voice* pxVoice = Asura_Audio_System::GetVoice(m_iSoundSystemHandle);
		const bool bPlaying = (pxVoice ? pxVoice->IsPlaying() : false);
	#else
		const bool bPlaying = Asura_Sound_System::SoundIsPlaying(m_iSoundSystemHandle);
	#endif

	if(m_fSubtitleTimer > 0.0f || bPlaying)
	{
		StopPlaying();
	}

}


const Asura_Entity_Client* Asura_Dialogue_Controller::FindEntity() const 
{
	//check for a client entity
	const Asura_Entity_Client* pxClient = Asura_Entity_Client::FindEntity(m_uCurrentSpeaker);

	if (!pxClient || !pxClient->GetCanPlayDialogue())
	{
		//Not present or not able to play dialogue (by default, the latter means it's a dead physical object).
		//return NULL in either case so that the dialogue will end up stopping if it already started.
		return NULL;
	}

	return pxClient;
}

const Asura_Cutscene_Actor* Asura_Dialogue_Controller::FindActor() const
{
	// JAT 27/09/07 - this will find the actor in any cutscene (active or not)
	const Asura_Cutscene_Actor* pxActor = Asura_Cutscene_System::FindCutsceneActor( m_uCurrentSpeaker );
	return pxActor;
}

bool Asura_Dialogue_Controller::GetSoundPosition( Asura_Vector_3& xPos ) const
{
	Asura_Assert(m_pxOwner,"");


	//Check for the owning entity or cutscene actor
	if(m_uCurrentSpeaker != ASURA_GUID_UNREGISTERED)
	{
		bool bFoundSpeaker = false;
		
		const Asura_Entity_Client* pxEntity = FindEntity();
		if (pxEntity)
		{
			bFoundSpeaker = pxEntity->GetPositionForDialogue(xPos);
			Asura_Assertf( bFoundSpeaker, ( "Entity %d (Classification %s) does not support character mappings", pxEntity->GetGuid(), Asura_Network::GetEntityClassificationDesc( pxEntity->GetClassification() ) ) );
		}
		if(!bFoundSpeaker)
		{
			const Asura_Cutscene_Actor *pxActor = FindActor();
			if(pxActor)
			{
				xPos = pxActor->Position;
				bFoundSpeaker = true;
			}
		}

		if(!bFoundSpeaker) return false;

		//position set directly?
		if(m_pxOwner->IsUsingPositionOverride())
		{
			xPos = m_pxOwner->GetPositionOverride();
		}

		return true;
	}

	//position set directly?
	if(m_pxOwner->IsUsingPositionOverride())
	{
		xPos = m_pxOwner->GetPositionOverride();
		return true;
	}


	return false;
}

#endif // #ifdef ASURA_USE_DIALOGUE_SYSTEM
