#include "Core/Asura.h"

#include "Cutscene/Asura_Cutscene_System.h"

#include "Core/Asura_NameTree.h"
#include "Core/Asura_Stream.h"
#include "Core/Asura_Timers.h"
#include "Camera/Asura_Camera.h"
#include "Cutscene/Asura_Cutscene.h"
#include "Cutscene/Asura_Cutscene_Actor.h"
#include "Debugging/Asura_Assert.h"
#include "Entity/Asura_Entity_Classification.h"
#include "Entity/Cutscene/Asura_ServerEntity_CutsceneController.h"
#ifdef ASURA_SUPPORT_COMMAND_CONSOLE
#include "Interface/Asura_CommandConsole.h"
#endif
#include "Network/Asura_Guid.h"
#include "Network/Asura_Message.h"
#include "Network/Asura_Network.h"
#include "Network/Asura_Player.h"
#include "Rendering/Asura_Render.h"
#include "Rendering/Asura_Viewport.h"         // for drawing widescreen borders

#ifdef ASURA_TOOLS
#include "Tools_System.h"
#endif

// M A C R O S ///////////////////////////////////////////////////////////////

#ifdef ASURA_SUPPORT_COMMAND_CONSOLE
#define CONSOLECMD_PROFILECUTSCENE L"profilecutscene"
#endif


// T Y P E S /////////////////////////////////////////////////////////////////


// C O N S T A N T S /////////////////////////////////////////////////////////


// G L O B A L S /////////////////////////////////////////////////////////////


// P R O T O T Y P E S   A N D   I N L I N E   F U N C T I O N S /////////////

#if defined(ASURA_SUPPORT_COMMAND_CONSOLE) && defined(ASURA_TOOLS)
static bool ProfileCutscene( const char* szCutsceneName, bool bInterrogate, bool bHelpRequested );
#endif


///////////////////////////
// Asura_Cutscene_System //
///////////////////////////
#if defined(ASURA_USE_MULTIPLE_HANDLERS)
Asura_Cutscene_Handler*							Asura_Cutscene_System::s_pxCurrentHandler = NULL;
Asura_TemplateHashTree<Asura_Cutscene_Handler>	Asura_Cutscene_System::s_xHandlerTree;
#else
Asura_Cutscene_Handler							Asura_Cutscene_System::s_xHandler;
#endif

u_int											Asura_Cutscene_System::s_uWidescreenBarColour = 0xff000000;
float											Asura_Cutscene_System::s_fWidescreenRatio = 2.35f / 1.0f;	// default, can be set to whatever you like
u_int											Asura_Cutscene_System::s_uSubtitleFontIndex = 0;			// default for backwards compatibility
void											(*Asura_Cutscene_System::s_pfnRenderWidescreenBars)() = NULL;
Asura_Cutscene*									Asura_Cutscene_System::s_pxCutscenePlayingSoundTrack = NULL;

float											Asura_Cutscene_System::s_fCutsceneSkipTime = -1.0f;

void Asura_Cutscene_System::Initialise(void)
{
#if !defined(ASURA_USE_MULTIPLE_HANDLERS)
	s_xHandler.Initialise();
#endif

#if defined(ASURA_SUPPORT_COMMAND_CONSOLE) && defined(ASURA_TOOLS)
	Asura_CommandConsole::AddCmd( CONSOLECMD_PROFILECUTSCENE, ProfileCutscene );
#endif

}

void Asura_Cutscene_System::Shutdown(void)
{
#if defined(ASURA_USE_MULTIPLE_HANDLERS)
	// Just free up the whole tree
	s_xHandlerTree.Reset();
#else 
	s_xHandler.Shutdown();
#endif 
}

void Asura_Cutscene_System::Reset(void)
{
#if defined(ASURA_USE_MULTIPLE_HANDLERS)
	Asura_Cutscene_Handler* pxStoredHandler = s_pxCurrentHandler;
	s_xHandlerTree.Traverse(Reset_Callback);
    SetCurrentHandler( pxStoredHandler );
#else 
	GetCurrentHandler().Reset();
#endif

}

void Asura_Cutscene_System::Update()
{
#if defined(ASURA_USE_MULTIPLE_HANDLERS)
// (MP - 28.07.2006) Some code for checking there haven't been any mix-ups
// with where cutscenes are being referenced. This was happening previously
// when the current handler wasn't always being set properly. It requires
// some hacks to Asura_TemplateHashTree to enable the GetDataByIndex() stuff.
//  // Check multiple handlers aren't referring to the same cutscene.
//  {
//      int iNumHandlers = s_xHandlerTree.GetNumberOfNodes();
//      for( int iHandler = 0; iHandler < iNumHandlers; ++iHandler )
//      {
//          Asura_Cutscene_Handler* pxHandler = s_xHandlerTree.GetDataByIndex( iHandler );
//
//          int iNumCutscenes = pxHandler->m_xCutsceneStorage.GetNumberOfNodes();
//          for( int iCutscene = 0; iCutscene < iNumCutscenes; ++iCutscene )
//          {
//              const Asura_Cutscene* pxCutscene = *(pxHandler->m_xCutsceneStorage.GetDataByIndex(iCutscene));
//              Asura_Hash_ID uCutsceneNameHash = Asura_GetHashID( pxCutscene->CutsceneName.GetString() );
//
//              for( int iOtherHandler = iHandler + 1; iOtherHandler < iNumHandlers; ++iOtherHandler )
//              {
//                  Asura_Cutscene_Handler* pxOtherHandler = s_xHandlerTree.GetDataByIndex( iOtherHandler );
//                  Asura_Assert( !pxOtherHandler->GetCurrentCutsceneByHash(uCutsceneNameHash) && !pxOtherHandler->GetCutsceneByHash(uCutsceneNameHash), "Multiple handlers pointing at the same cutscene" );
//              }
//          }
//      }
//  }

	// Update ALL cutscene systems...
	Asura_Cutscene_Handler* pxStoredHandler = s_pxCurrentHandler;
	s_xHandlerTree.Traverse(Update_Callback);
    SetCurrentHandler( pxStoredHandler );
#else
	// Update the cutscene system...
	s_xHandler.Update();
#endif	// defined(ASURA_USE_MULTIPLE_HANDLERS)

}

// AJG - 28/03/06 - First version: stop all cutscenes in all handlers, making the assumption that when this is called we're wanting to kill off everything in all zones.
void Asura_Cutscene_System::StopAllCutscenes()
{
#if defined(ASURA_USE_MULTIPLE_HANDLERS)
	// To ensure we clear all cutscenes correctly, including those in the finishing and starting trees, we'll need to set the current handler as we go past. Hence, backup the actual current one here.
	Asura_Cutscene_Handler* pxStoredHandler = s_pxCurrentHandler;

	s_xHandlerTree.Traverse(StopAllCutscenes_Callback);

	s_pxCurrentHandler = pxStoredHandler;

#else

	s_xHandler.StopAllCutscenes();
#endif

}

void Asura_Cutscene_System::SetWidescreenRatio(float fX, float fY)
{
	s_fWidescreenRatio = fX / fY;
}

float Asura_Cutscene_System::GetWidescreenBarHeight()
{
	// this allows variable screen aspect - so if doing 16:9 cutscenes and the screen
	// is 16:9 you get 0-height bars, and for wider cutscenes you get smaller bars.
	float fX = s_fWidescreenRatio;
	float fY = 1.0f;
	float fYOverXScreenAspect = 1.0f / Asura_Camera::GetPhysicalScreenAspectRatio();
	float fUnnormalisedHeight = ((fX * fYOverXScreenAspect) - fY) / 2.0f;
	float fWidescreenBarHeight = fUnnormalisedHeight / ((2.0f * fUnnormalisedHeight) + fY);
	return fWidescreenBarHeight;
}

float Asura_Cutscene_System::GetWidescreenDisplayedXOverYRatio()
{
	float fDisplayedHeight = 1.0f - (2.0f * GetWidescreenBarHeight());
	return 1.0f / fDisplayedHeight;
}

bool Asura_Cutscene_System::ShouldRenderWidescreenBars()
{
	bool bWidescreen = false;
	if (Asura_Render::GetCutsceneHasCameraControl())
	{

		// see if any cutscene is widescreen.
#if defined(ASURA_USE_MULTIPLE_HANDLERS)
		s_xHandlerTree.Traverse(ShouldRenderWidescreenBars_Callback, &bWidescreen);

#else
		bWidescreen = s_xHandler.ShouldRenderWidescreenBars();
#endif
	}
	return bWidescreen;
}

void Asura_Cutscene_System::RenderWidescreenBars()
{
	if(s_pfnRenderWidescreenBars) //use the function that has been set for this
	{
		s_pfnRenderWidescreenBars();		
	}
	else //use the default bars
	{
    	// (MH - 25.11.2002) draw the widescreen borders. Should probably change the frustum clip as well...
    	// note that this may be called from other code such as GF2 menus, 
    	// which wants to simulate cutscene borders.
		Asura_Render::AllowFullScreenRendering();
    	float fBarHeight = Asura_Cutscene_System::GetWidescreenBarHeight();
    	float fScreenWidth = static_cast<float>( Asura_Viewport::GetWidth() );
    	float fScreenHeight = static_cast<float>( Asura_Viewport::GetHeight() );
    	Asura_Render::Overlay.Colour = s_uWidescreenBarColour;
    	Asura_Render::Overlay.TextureIndex = ASURA_TEXTURE_NONE;
    	Asura_Render::Overlay.TranslucencyMode = ASURA_TRANSLUCENCY_NORMAL;
    	Asura_Render::Overlay.SetRectXY(0.0f, 0.0f, fScreenWidth, fBarHeight * fScreenHeight);
    	Asura_Render::Overlay.Render();
    	Asura_Render::Overlay.SetRectXY(0.0f, (1.0f - fBarHeight) * fScreenHeight, fScreenWidth, fBarHeight * fScreenHeight);
    	Asura_Render::Overlay.Render();
    	Asura_Render::FlushDisplay();
	}
}

void Asura_Cutscene_System::RenderSubtitles_Callback(Asura_Hash_ID uHash, Asura_Cutscene** ppxCutscene)
{
	if (ppxCutscene && *ppxCutscene)
	{
		(*ppxCutscene)->RenderSubtitles();
	}
}

bool Asura_Cutscene_System::CheckAndRequestSkipCutscenes(float fMinPlayingTime)
{
	if (GetCanSkipCutscenes(fMinPlayingTime))
	{
		// Skip on this machine
		RequestSkipCutscenes();
		// And message other machines if we need to
		if (Asura_Network::IsExternalNetworkActive())
		{
			// First version - get the guid of any player on this machine, and send to all other machines.
			// However, we can only send to all other players from the server, so we have to go via there.
			Asura_Guid uFirstLocalPlayerGuid = ASURA_GUID_UNREGISTERED;

			Asura_Hashed_List_ConstIt<Asura_Entity_Player> xPlayerIt(Asura_Entity_Player::GetList());
			if (!xPlayerIt.Done())
			{
				const Asura_Entity_Player* pxFirstLocalPlayer = xPlayerIt.GetCurrent();
				if (pxFirstLocalPlayer)
				{
					uFirstLocalPlayerGuid = pxFirstLocalPlayer->GetGuid();
				}
			}

			Asura_Message* pxMessage = Asura_Network::CreateMessage(ASURA_MSG_SKIP_CUTSCENES);
			pxMessage->From = uFirstLocalPlayerGuid;
			pxMessage->To = ASURA_GUID_SERVER;

			Asura_Network::Send(pxMessage);
		}

		return true;
	}

	return false;
}

bool Asura_Cutscene_System::GetCanSkipCutscenes(float fMinPlayingTime)
{
	s_fCutsceneSkipTime = fMinPlayingTime;
	bool bCanSkip = true;

#if defined(ASURA_USE_MULTIPLE_HANDLERS)
	s_xHandlerTree.Traverse(GetCanSkipCutscenes_Callback, &bCanSkip);
#else
	bCanSkip = s_xHandler.GetCanSkipCutscenes();
#endif

	s_fCutsceneSkipTime = -1.0f;
	return bCanSkip;
}

void Asura_Cutscene_System::RequestSkipCutscenes()
{
#if defined(ASURA_USE_MULTIPLE_HANDLERS)
	s_xHandlerTree.Traverse(SkipCutscenes_Callback);
#else
	s_xHandler.SkipCutscenes();
#endif
}

void Asura_Cutscene_System::TraverseAllCutscenes(bool (*pfnCallback)(Asura_Hash_ID, class Asura_Cutscene**, void*), void* pUserData, bool bAllHandlers/* = false*/)
{
	Asura_Cutscene_Handler::s_pfnGenericCutsceneCallbackWithData = pfnCallback;
	Asura_Cutscene_Handler::s_pGenericCutsceneCallbackUserData = pUserData;

#if defined(ASURA_USE_MULTIPLE_HANDLERS)
	if (bAllHandlers)
	{
	    Asura_Cutscene_Handler* pxStoredHandler = s_pxCurrentHandler;
		s_xHandlerTree.Traverse(TraverseAllCutscenes_Callback);
        SetCurrentHandler( pxStoredHandler );
	}
	else
#endif
	{
		GetCurrentHandler().TraverseAllCutscenes();
	}

	Asura_Cutscene_Handler::s_pfnGenericCutsceneCallbackWithData = NULL;
	Asura_Cutscene_Handler::s_pGenericCutsceneCallbackUserData = NULL;
}

void Asura_Cutscene_System::TraverseAllCutscenes(void (*pfnCallback)(Asura_Hash_ID, class Asura_Cutscene**), bool bAllHandlers/* = false*/)
{
	Asura_Cutscene_Handler::s_pfnGenericCutsceneCallbackWithoutData = pfnCallback;

#if defined(ASURA_USE_MULTIPLE_HANDLERS)
	if (bAllHandlers)
	{
	    Asura_Cutscene_Handler* pxStoredHandler = s_pxCurrentHandler;
		s_xHandlerTree.Traverse(TraverseAllCutscenes_Callback);
        SetCurrentHandler( pxStoredHandler );
	}
	else
#endif
	{
		GetCurrentHandler().TraverseAllCutscenes();
	}

	Asura_Cutscene_Handler::s_pfnGenericCutsceneCallbackWithoutData = NULL;
}

void Asura_Cutscene_System::TraverseCurrentCutscenes(bool (*pfnCallback)(Asura_Hash_ID, class Asura_Cutscene**, void*), void* pUserData, bool bAllHandlers/* = false*/)
{
	Asura_Cutscene_Handler::s_pfnGenericCutsceneCallbackWithData = pfnCallback;
	Asura_Cutscene_Handler::s_pGenericCutsceneCallbackUserData = pUserData;

#if defined(ASURA_USE_MULTIPLE_HANDLERS)
	if (bAllHandlers)
	{
	    Asura_Cutscene_Handler* pxStoredHandler = s_pxCurrentHandler;
		s_xHandlerTree.Traverse(TraverseCurrentCutscenes_Callback);
        SetCurrentHandler( pxStoredHandler );
	}
	else
#endif
	{
		GetCurrentHandler().TraverseCurrentCutscenes();
	}

	Asura_Cutscene_Handler::s_pfnGenericCutsceneCallbackWithData = NULL;
	Asura_Cutscene_Handler::s_pGenericCutsceneCallbackUserData = NULL;
}

void Asura_Cutscene_System::TraverseCurrentCutscenes(void (*pfnCallback)(Asura_Hash_ID, class Asura_Cutscene**), bool bAllHandlers/* = false*/)
{
	Asura_Cutscene_Handler::s_pfnGenericCutsceneCallbackWithoutData = pfnCallback;

#if defined(ASURA_USE_MULTIPLE_HANDLERS)
	if (bAllHandlers)
	{
	    Asura_Cutscene_Handler* pxStoredHandler = s_pxCurrentHandler;
		s_xHandlerTree.Traverse(TraverseCurrentCutscenes_Callback);
        SetCurrentHandler( pxStoredHandler );
	}
	else
#endif
	{
		GetCurrentHandler().TraverseCurrentCutscenes();
	}

	Asura_Cutscene_Handler::s_pfnGenericCutsceneCallbackWithoutData = NULL;
}

#if defined(ASURA_USE_MULTIPLE_HANDLERS)
void Asura_Cutscene_System::Update_Callback( Asura_Hash_ID uHash, Asura_Cutscene_Handler* pxData )
{
    if( pxData )
    {
        SetCurrentHandler( pxData );
        pxData->Update();
    }
}

void Asura_Cutscene_System::Reset_Callback(Asura_Hash_ID uHash, Asura_Cutscene_Handler* pxData)
{
	// Don't need the hash, just reset the data in the pointer
	if (pxData)
	{
        SetCurrentHandler( pxData );
		pxData->Reset();
	}
}

void Asura_Cutscene_System::StopAllCutscenes_Callback(Asura_Hash_ID uHash, Asura_Cutscene_Handler* pxData)
{
	// Don't need the hash, just use the data in the pointer
	if (pxData)
	{
		// Set the current handler so we can traverse the initialising and finishing trees.
		SetCurrentHandler(pxData);
		pxData->StopAllCutscenes();
	}
}

bool Asura_Cutscene_System::GetCanSkipCutscenes_Callback(Asura_Hash_ID uHash, class Asura_Cutscene_Handler* pxData, void* pUserData)
{
	// Don't need the hash, just use the data in the pointer
	if (pxData)
	{
		bool* pbCanSkip = static_cast<bool*>(pUserData);

		*pbCanSkip &= pxData->GetCanSkipCutscenes();

		// Continue unless we've found something that can't be skipped.
		return *pbCanSkip;
	}

	return true;
}

void Asura_Cutscene_System::SkipCutscenes_Callback(Asura_Hash_ID uHash, Asura_Cutscene_Handler* pxData)
{
	// Don't need the hash, just use the data in the pointer
	if (pxData)
	{
		pxData->SkipCutscenes();
	}
}

bool Asura_Cutscene_System::ShouldRenderWidescreenBars_Callback(Asura_Hash_ID uHash, class Asura_Cutscene_Handler* pxData, void* pUserData)
{
	// Ignore the hash, just use the data
	if (pxData)
	{
		bool* pbWidescreen = static_cast<bool*>(pUserData);
		*pbWidescreen = pxData->ShouldRenderWidescreenBars();
		// Break out if we've found a widescreen cutscene
		return !(*pbWidescreen);
	}
	return true;
}

void Asura_Cutscene_System::TraverseAllCutscenes_Callback(Asura_Hash_ID uHash, class Asura_Cutscene_Handler* pxData)
{
	if (pxData)
	{
        SetCurrentHandler( pxData );
		pxData->TraverseAllCutscenes();
	}
}

void Asura_Cutscene_System::TraverseCurrentCutscenes_Callback(Asura_Hash_ID uHash, class Asura_Cutscene_Handler* pxData)
{
	if (pxData)
	{
        SetCurrentHandler( pxData );
		pxData->TraverseCurrentCutscenes();
	}
}

void Asura_Cutscene_System::ReparentHandler(Asura_Cutscene_Handler& xSourceHandler, const Asura_Hash_ID uNewParentHash)
{
	Asura_Cutscene_Handler*	pxParentHandler	= s_xHandlerTree.GetDataByHash(uNewParentHash);

	if(pxParentHandler != NULL)
	{
		xSourceHandler.m_xCutsceneStorage.SetParent(&(pxParentHandler->m_xCutsceneStorage));
		xSourceHandler.m_xCurrentCutscenes.SetParent(&(pxParentHandler->m_xCurrentCutscenes));
		xSourceHandler.m_xFinishingCutscenes.SetParent(&(pxParentHandler->m_xFinishingCutscenes));
		xSourceHandler.m_xInitialisingCutscenes.SetParent(&(pxParentHandler->m_xInitialisingCutscenes));
	}
}

void Asura_Cutscene_System::ReparentHandler(const Asura_Hash_ID uSourceHash, const Asura_Hash_ID uNewParentHash)
{
	Asura_Cutscene_Handler* pxHandler = s_xHandlerTree.GetDataByHash(uSourceHash);

	if(pxHandler != NULL)
	{
		ReparentHandler(*pxHandler, uNewParentHash);
	}
}

////////////////////////////////////////////////////////////////////////////////
// Function     : Asura_Cutscene_System::CreateHandler
// Author       : Anthony Gregson
// Date         : 30-Mar-2006
//
// Description  : Attempts to create a cutscene system handler with the specified
//				  hash-ID.
//
// Parameters   : The hash-ID of the handler to create.
//
// Return value : A pointer to the handler created.
////////////////////////////////////////////////////////////////////////////////

Asura_Cutscene_Handler* Asura_Cutscene_System::CreateHandler(const Asura_Hash_ID uHash, const Asura_Hash_ID uParentHandlerHash)
{
	Asura_Cutscene_Handler* pxHandler = s_xHandlerTree.GetDataByHash(uHash);

	// Do we already have this handler?
	if(pxHandler == NULL)
	{
		// No - create one...
		pxHandler = s_xHandlerTree.AddNode(uHash);

		if(pxHandler != NULL)
		{
			ReparentHandler(*pxHandler, uParentHandlerHash);
		}
	}
	else
	{
		// Yes - this shouldn't happen...
		Asura_Assert(false, "Already have a cutscene handler with this hash ID.");
	}

	// Return the handler...
	return(pxHandler);
}

/// End of function 'Asura_Cutscene_System::CreateHandler'. ///



////////////////////////////////////////////////////////////////////////////////
// Function     : Asura_Cutscene_System::DestroyHandler
// Author       : Anthony Gregson
// Date         : 30-Mar-2006
//
// Description  : Attempts to destroy the cutscene system handler with the specified
//				  unique hash-ID.
//
// Parameters   : The hash-ID of the handler to destroy.
//
// Return value : "true" if destroyed, "false" if failed.
////////////////////////////////////////////////////////////////////////////////

bool Asura_Cutscene_System::DestroyHandler(const Asura_Hash_ID uHash)
{
	// Save off the current handler, set current handler to this, do the destroy, then set back
	Asura_Cutscene_Handler* pxStoredHandler = s_pxCurrentHandler;
	Asura_Cutscene_Handler* pxDestroyingHandler = s_xHandlerTree.GetDataByHash(uHash);
	// Is this the current handler?
	if(pxDestroyingHandler == pxStoredHandler)
	{
		pxStoredHandler	= NULL;
	}

	s_pxCurrentHandler = pxDestroyingHandler;

	// Call the delete function...
	bool bSuccess = s_xHandlerTree.DeleteNodeByHash(uHash);

	s_pxCurrentHandler = pxStoredHandler;

	return bSuccess;
}

/// End of function 'Asura_Cutscene_System::DestroyHandler'. ///



////////////////////////////////////////////////////////////////////////////////
// Function     : Asura_Cutscene_System::WriteHandlerLog
// Author       : Paul Cardy
// Date         : 20-12-07
//
// Description  : Writes a log of the contents of the supplied handler
//
// Parameters   : The hash-ID of the handler to log and the filename to log to
//
// Return value : None
////////////////////////////////////////////////////////////////////////////////

void Asura_Cutscene_System::WriteHandlerReport(u_int uHandler, Asura_Stream& xLog)
{
	const u_int uLOGBUFFERSIZE = 64;
	char szLogBuffer[uLOGBUFFERSIZE];

	Asura_Cutscene_Handler* pxHandler = s_xHandlerTree.GetDataByHash(uHandler);
	if (pxHandler)
	{
		if (!pxHandler->m_xCutsceneStorage.GetNumberOfNodes())
		{
			xLog << "Cutscene:\t\t\tNone\n";
		}
		else
		{
			Asura_TemplateHashTree_Iterator<Asura_Cutscene*> xIt(pxHandler->m_xCutsceneStorage);
			for ( ; !xIt.Done(); xIt.Next() )
			{
				Asura_Cutscene** ppxCutscene = xIt.GetCurrent();
				Asura_PrintfToBfr(szLogBuffer, "Cutscene:\t\t\t%s\n", uLOGBUFFERSIZE, true, (*ppxCutscene)->GetName().GetString());
				xLog << szLogBuffer;
			}		
		}
	}
	else
	{
		xLog << "Cutscene:\t\t\tNo Handler\n";
	}
}

/// End of function 'Asura_Cutscene_System::WriteHandlerLog'. ///

#endif //#if defined(ASURA_USE_MULTIPLE_HANDLERS)

void Asura_Cutscene_System::SetCutscenePlayingSoundTrack(Asura_Cutscene* pxCutscene, bool bSet)
{
	if(bSet)
	{
		Asura_Assert(s_pxCutscenePlayingSoundTrack == 0, "Another cutscene is already playing a soundtrack.");
		s_pxCutscenePlayingSoundTrack = pxCutscene;
	}
	else if(pxCutscene == s_pxCutscenePlayingSoundTrack)
	{
		s_pxCutscenePlayingSoundTrack = 0;
	}
}

bool Asura_Cutscene_System::ShouldPauseStreamsForCutsceneSoundTrack()
{
	if(s_pxCutscenePlayingSoundTrack)
	{
		if(!(s_pxCutscenePlayingSoundTrack->m_iCutsceneFlags & AsuraCutsceneFlag_DontPauseStreams))
		{
			return true;
		}
	}
	return false;
}


////////////////////////////
// Asura_Cutscene_Handler //
////////////////////////////
Asura_Guid Asura_Cutscene_Handler::s_uFindCutsceneActorGuid = ASURA_GUID_UNREGISTERED; 
void (*Asura_Cutscene_Handler::s_pfnGenericCutsceneCallbackWithoutData)(Asura_Hash_ID uHash, class Asura_Cutscene** ppxCutscene) = NULL;
bool (*Asura_Cutscene_Handler::s_pfnGenericCutsceneCallbackWithData)(Asura_Hash_ID uHash, class Asura_Cutscene** ppxCutscene, void* pUserData) = NULL;
void* Asura_Cutscene_Handler::s_pGenericCutsceneCallbackUserData = NULL;

Asura_Cutscene_Handler::Asura_Cutscene_Handler()
{
	// AJG - 27/03/06 - Non-streaming one doesn't need to initialise at this point
#if defined(ASURA_USE_MULTIPLE_HANDLERS)
	Initialise();
#endif
}

Asura_Cutscene_Handler::~Asura_Cutscene_Handler()
{
	// AJG - 27/03/06 - Non-streaming one doesn't need to shutdown at this point
#if defined(ASURA_USE_MULTIPLE_HANDLERS)
	Shutdown();
#endif
}

void Asura_Cutscene_Handler::Initialise(void)
{
	// Nothing to do here, the trees are constructed/reset to empty
}

void Asura_Cutscene_Handler::Shutdown(void)
{
	// AJG - 30/03/06 - This can do the same as Reset
	Reset();
}

void Asura_Cutscene_Handler::Reset(void)
{
	StopAllCutscenes();

	// Delete all cutscene pointers
	m_xCutsceneStorage.Traverse(DestroyCutscene_Callback);
	// And clear the tree
	m_xCutsceneStorage.Reset();
}

void Asura_Cutscene_Handler::DestroyCutscene_Callback(Asura_Hash_ID uHash, class Asura_Cutscene** ppxCutscene)
{
	if (ppxCutscene && *ppxCutscene)
	{
		delete *ppxCutscene;
	}
}

void Asura_Cutscene_Handler::Update()
{
	// TODO - is it correct to traverse the starting/finishing cutscenes before the update, after the update, or both? Needs testing.

	// Ensure everything that should and shouldn't be in the current cutscene list is there (calling pre-update ensures entity-affected ones are in here)
	if (m_xInitialisingCutscenes.GetNumberOfNodes() > 0)
	{
// (MP - 28.07.2006) Some code for checking there haven't been any mix-ups
// with where cutscenes are being referenced. This was happening previously
// when the current handler wasn't always being set properly. It requires
// some hacks to Asura_TemplateHashTree to enable the GetDataByIndex() stuff.
//      // Check some other handler isn't referring to any of these cutscenes.
//      {
//          int iNumInitialisingCutscenes = m_xInitialisingCutscenes.GetNumberOfNodes();
//          for( int iCutscene = 0; iCutscene < iNumInitialisingCutscenes; ++iCutscene )
//          {
//              const Asura_Cutscene* pxCutscene = *(m_xInitialisingCutscenes.GetDataByIndex(iCutscene));
//              Asura_Hash_ID uCutsceneNameHash = Asura_GetHashID( pxCutscene->CutsceneName.GetString() );
//
//              int iNumHandlers = Asura_Cutscene_System::s_xHandlerTree.GetNumberOfNodes();
//              for( int iOtherHandler = 0; iOtherHandler < iNumHandlers; ++iOtherHandler )
//              {
//                  Asura_Cutscene_Handler* pxOtherHandler = Asura_Cutscene_System::s_xHandlerTree.GetDataByIndex( iOtherHandler );
//                  if( pxOtherHandler == this ) { continue; }
//
//                  Asura_Assert( !pxOtherHandler->GetCurrentCutsceneByHash(uCutsceneNameHash) && !pxOtherHandler->GetCutsceneByHash(uCutsceneNameHash), "Another handler is pointing at this cutscene" );
//              }
//          }
//      }

		m_xInitialisingCutscenes.Traverse(AddToCurrentCutscenes_Callback);
		m_xInitialisingCutscenes.Reset();
	}
	if (m_xFinishingCutscenes.GetNumberOfNodes() > 0)
	{
		m_xFinishingCutscenes.Traverse(RemoveFromCurrentCutscenes_Callback);
		m_xFinishingCutscenes.Reset();
	}

	#ifdef ASURA_TOOLS
	//Per-frame update of cutscenes is handled by cutscene mode while in tools. RAR 21/04/2006
	if(!Tools_System::IsActive())
	#endif
	{
		m_xCurrentCutscenes.Traverse(Update_Callback);
	}

/*
	// Ensure everything that should and shouldn't be in the current cutscene list is there (calling post-update ensures cutscene-affected ones are in here)
	if (m_xInitialisingCutscenes.GetNumberOfNodes() > 0)
	{
		m_xInitialisingCutscenes.Traverse(AddToCurrentCutscenes_Callback);
		m_xInitialisingCutscenes.Reset();
	}
	if (m_xFinishingCutscenes.GetNumberOfNodes() > 0)
	{
		m_xFinishingCutscenes.Traverse(RemoveFromCurrentCutscenes_Callback);
		m_xFinishingCutscenes.Reset();
	}
*/
}

void Asura_Cutscene_Handler::Update_Callback(Asura_Hash_ID uHash, class Asura_Cutscene** ppxCutscene)
{
	if (ppxCutscene && *ppxCutscene)
	{
		Asura_Cutscene* pxCutscene = *ppxCutscene;
		if (pxCutscene->PlaysOnServer && pxCutscene->Playing)
		{
			pxCutscene->Update(Asura_Timers::GetGameTimeSlice());
		}
	}
}

Asura_Cutscene* Asura_Cutscene_Handler::GetCutsceneByHash( const Asura_Hash_ID uHash )
{
	Asura_Cutscene** ppxResult = m_xCutsceneStorage.GetDataByHash(uHash);
	if (ppxResult)
	{
		return *ppxResult;
	}
	return NULL;
}

bool Asura_Cutscene_Handler::AddCutscene(Asura_Cutscene* pxCutscene)
{
	Asura_Hash_ID uHash = Asura_GetHashID(pxCutscene->CutsceneName.GetString());

#if defined(ASURA_USE_MULTIPLE_HANDLERS)
	if (m_xCutsceneStorage.GetDataByHash(uHash))
	{
		Asura_Assertf(false,("Not adding duplicate cutscene %s",pxCutscene->CutsceneName.GetString()));
		return false;
	}
#endif
	
	return m_xCutsceneStorage.AddNode(uHash, pxCutscene) != NULL;
}

#ifdef ASURA_TOOLS
int Asura_Cutscene_Handler::GetCutsceneIndex( const char* szName )
{
	return m_xCutsceneStorage.GetIndexOfHash(Asura_GetHashID(szName));
}
#endif //#ifdef ASURA_TOOLS

void Asura_Cutscene_Handler::DeleteCutsceneByHash( const Asura_Hash_ID uHash )
{
	// Ensure it's not in the active list too
	m_xCurrentCutscenes.DeleteNodeByHash(uHash);

	//delete the cutscene
	delete GetCutsceneByHash(uHash);

	//and remove from the tree	
	#if defined ASURA_ASSERT
		const bool bSuccess =
	#endif
		m_xCutsceneStorage.DeleteNodeByHash(uHash);

	Asura_Assert(bSuccess, "failed to delete cutscene from storage");
}

void Asura_Cutscene_Handler::CutsceneStarted(Asura_Cutscene* pxCutscene)
{
	// TODO - Can we do this without the get hash id call?
	// Get the hash, see if we're already finishing it (in which case remove it from that list, as they cancel out), and add it to the initialising list if not.
	Asura_Hash_ID uHash = Asura_GetHashID(pxCutscene->CutsceneName.GetString());

	if (!m_xFinishingCutscenes.DeleteNodeByHash(uHash))
	{
		// Didn't remove it from the finishing, so it wasn't already finishing, so we need to add it to the initialising list
		m_xInitialisingCutscenes.AddNode(uHash, pxCutscene);
	}
}

void Asura_Cutscene_Handler::CutsceneFinished(Asura_Cutscene* pxCutscene)
{
	// TODO - Can we do this without the get hash id call?
	// Get the hash, see if we're already initialising it (in which case remove it from that list, as they cancel out), and add it to the finishing list if not.
	Asura_Hash_ID uHash = Asura_GetHashID(pxCutscene->CutsceneName.GetString());

	if (!m_xInitialisingCutscenes.DeleteNodeByHash(uHash))
	{
		// Didn't remove it from the initialising, so it wasn't already initialising, so we need to add it to the finishing list
		m_xFinishingCutscenes.AddNode(uHash, pxCutscene);
	}
}

void Asura_Cutscene_Handler::AddToCurrentCutscenes_Callback(Asura_Hash_ID uHash, Asura_Cutscene** ppxCutscene)
{
	if (ppxCutscene && *ppxCutscene)
	{
		Asura_Cutscene_System::GetCurrentHandler().AddToCurrentCutscenes(uHash, *ppxCutscene);
	}
}

void Asura_Cutscene_Handler::AddToCurrentCutscenes(Asura_Hash_ID uHash, Asura_Cutscene* pxCutscene)
{
	m_xCurrentCutscenes.AddNode(uHash, pxCutscene);
}

void Asura_Cutscene_Handler::RemoveFromCurrentCutscenes_Callback(Asura_Hash_ID uHash, Asura_Cutscene** ppxCutscene)
{
	Asura_Cutscene_System::GetCurrentHandler().RemoveFromCurrentCutscenes(uHash);
}

void Asura_Cutscene_Handler::RemoveFromCurrentCutscenes(Asura_Hash_ID uHash)
{
	m_xCurrentCutscenes.DeleteNodeByHash(uHash);
}

void Asura_Cutscene_Handler::StopAllCutscenes(void)
{
	// Make sure all cutscenes that have called their initialise function are in the list, and all that have called their finish function aren't
	m_xInitialisingCutscenes.Traverse(AddToCurrentCutscenes_Callback);
	m_xInitialisingCutscenes.Traverse(RemoveFromCurrentCutscenes_Callback);
	// Traverse the list to stop all elements
	m_xCurrentCutscenes.Traverse(StopAllCutscenes_Callback);
	// And the clear the lists
	m_xCurrentCutscenes.Reset();
	m_xInitialisingCutscenes.Reset();
	m_xFinishingCutscenes.Reset();
}

void Asura_Cutscene_Handler::StopAllCutscenes_Callback(Asura_Hash_ID uHash, class Asura_Cutscene** ppxCutscene)
{
	// ignore the hash, just use the data
	if (ppxCutscene && *ppxCutscene)
	{
		// Skip the CutsceneFinished call in Finish.
		(*ppxCutscene)->Finish();
	}
}

void Asura_Cutscene_Handler::CutsceneProfileRequestedByClient( Asura_Hash_ID uCutsceneHash )
{
	// Only applicable to server.
	if( !Asura_Network::IsServer() )
	{
		Asura_Assert( false, "Clients shouldn't receive this message." );
		return;
	}

	// Ignore this message if any cutscenes that control the camera - ie.
	// take control away from the player - are already playing.
	// AJG - 28/03/06 - I don't see a reason why the flag in Asura_Render should be invalid, so try just checking that
	if (Asura_Render::GetCutsceneHasCameraControl()) { return; }
//	for( int i = 0; i < GetNumberOfCurrentCutscenes(); ++i )
//	{
//		if( (GetCurrentCutscene(i))->ControlsCamera() ) { return; }
//	}

	// Find corresponding cutscene controller.
	const Asura_Cutscene* pxCutscene = GetCutsceneByHash( uCutsceneHash );
	if (!pxCutscene) { return; }

	for( Asura_Classified_List_Const_It<Asura_Entity_Server> xIt(Asura_Entity_Server::GetList()); !xIt.Done(); xIt.Next() )
	{
		const Asura_Entity_Server* pxEntity = xIt.GetCurrent();

		// Only interested in cutscene controllers.
		if( AsuraEntityClass_CutsceneController != pxEntity->GetClassification() ) { continue; }

		// Get proper ptr.
		const Asura_ServerEntity_CutsceneController* pxController = reinterpret_cast<const Asura_ServerEntity_CutsceneController*>(pxEntity);

		// Compare name.
		if( pxCutscene->CutsceneName.Compare(*pxController->GetCutsceneName()) ) { continue; }

		// Okay, this is the one. Send a message to it.
		Asura_Message* pxMsg = Asura_Network::CreateMessage( ASURA_MSG_CUTSCENECONTROLLER_REQ_PROFILE_CUTSCENE );
		pxMsg->To = pxController->GetGuid();
		pxMsg->From = ASURA_GUID_SERVER;
		Asura_Network::Send( pxMsg );

		// And we're done.
		break;
	}
}

bool Asura_Cutscene_Handler::ShouldRenderWidescreenBars()
{
	bool bWidescreen = false;
	m_xCurrentCutscenes.Traverse(ShouldRenderWidescreenBars_Callback, &bWidescreen);
	return bWidescreen;
}

bool Asura_Cutscene_Handler::ShouldRenderWidescreenBars_Callback(Asura_Hash_ID uHash, class Asura_Cutscene** ppxCutscene, void* pUserData)
{
	// ignore the hash, just use the data
	if (ppxCutscene && *ppxCutscene)
	{
		if ((*ppxCutscene)->ShouldRenderWidescreenBars())
		{
			bool* pbWidescreen = static_cast<bool*>(pUserData);
			*pbWidescreen = true;
			// Break out of the search
			return false;
		}
	}
	return true;
}

void Asura_Cutscene_Handler::RenderSubtitles()
{
	if (Asura_Render::GetCutsceneHasCameraControl())
	{
		m_xCurrentCutscenes.Traverse(RenderSubtitles_Callback);
	}
}

void Asura_Cutscene_Handler::RenderSubtitles_Callback(Asura_Hash_ID uHash, class Asura_Cutscene** ppxCutscene)
{
	// ignore the hash, just use the data
	if (ppxCutscene && *ppxCutscene)
	{
		(*ppxCutscene)->RenderSubtitles();
	}
}

bool Asura_Cutscene_Handler::RenameCutscene(const char* szOldName, const char* szNewName)
{
	//get the cutscene to be renamed
	Asura_Hash_ID uOld = Asura_GetHashID(szOldName);
	Asura_Hash_ID uNew = Asura_GetHashID(szNewName);
	Asura_Cutscene* pxCutscene = GetCutsceneByHash(uOld);
	if(!pxCutscene) return false;

	//make sure a cutscene with the new name doesn't exist
	if(GetCutsceneByHash(uNew)) return false;

	//update the hash in the hash tree
	if(!m_xCutsceneStorage.ReplaceHash(uOld, uNew)) return false;
	// Also update the hash in the currently playing, although this is expected to always fail so no need to break out if it does
	m_xCurrentCutscenes.ReplaceHash(uOld, uNew);

	//update the name in the cutscene
	pxCutscene->CutsceneName = szNewName;

	return true;
}


#ifdef ASURA_TOOLS
void Asura_Cutscene_Handler::BuildUsedTextureTree(Asura_NameTree* pxNameTree)
{
	m_xCutsceneStorage.Traverse(BuildUsedTextureTree_Callback, pxNameTree);
}

bool Asura_Cutscene_Handler::BuildUsedTextureTree_Callback(Asura_Hash_ID uHash, class Asura_Cutscene** ppxCutscene, void* pUserData)
{
	// ignore the hash, just use the data
	if (ppxCutscene && *ppxCutscene)
	{
		(*ppxCutscene)->BuildUsedTextureTree(static_cast<Asura_NameTree*>(pUserData));
	}
	// No early exits
	return true;
}
#endif

bool Asura_Cutscene_Handler::AnyOpeningCutscenes( void )
{
	return GetCutsceneStartPosition();
}

bool Asura_Cutscene_Handler::GetCutsceneStartPosition( Asura_Vector_3* pxPos, const Asura_Cutscene** ppxCutscene )
{
	const Asura_Cutscene* pxCutscene = NULL;
	m_xCutsceneStorage.Traverse(GetCutsceneStartPosition_Callback, &pxCutscene);
	
	if (pxCutscene)
	{
		if( pxPos )
		{
			const Asura_Cutscene_Actor_Camera* pxCamActor = NULL;
			for( int iActor = 0; iActor < pxCutscene->NumberOfActors; ++iActor )
			{
				if( pxCutscene->Actors[iActor]->IsCamera() )
				{
					pxCamActor = reinterpret_cast<const Asura_Cutscene_Actor_Camera*>( pxCutscene->Actors[iActor] );
					break;
				}
			}
			Asura_Assert( pxCamActor, "Cutscene controls camera yet has no camera actor?" );

			*pxPos = pxCamActor->GetStartPosition();
		}
	    
		if( ppxCutscene ) { *ppxCutscene = pxCutscene; }

		return true;
	}

	return false;
}

bool Asura_Cutscene_Handler::GetCutsceneStartPosition_Callback(Asura_Hash_ID uHash, class Asura_Cutscene** ppxCutscene, void* pUserData)
{
	// Return true unless we find an appropriate cutscene, then set the data and return false so we break out.
	if (!ppxCutscene || !*ppxCutscene) { return true; }
	Asura_Cutscene* pxCutscene = *ppxCutscene;
	if ((pxCutscene->m_iCutsceneFlags & AsuraCutsceneFlag_StartsActive) == 0) { return true; }
	if (!pxCutscene->ControlsCamera()) { return true; }

	const Asura_Cutscene** ppxCutsceneOut = static_cast<const Asura_Cutscene**>(pUserData);
	*ppxCutsceneOut = pxCutscene;
	return false;
}

//
// Asura_Cutscene_Handler::FindCutsceneActor()
//
// Parameters:		uGuid - The guid of the actor you want
// Return value:	Pointer to the cutscene actor, or NULL if not found
//
// This function finds the cutscene actor with the given guid
//

Asura_Cutscene_Actor* Asura_Cutscene_Handler::FindCutsceneActor(Asura_Guid uGuid)
{
	Asura_Cutscene_Actor* pxActor = NULL;
	s_uFindCutsceneActorGuid = uGuid;
	m_xCutsceneStorage.Traverse(FindCutsceneActor_Callback, &pxActor);

	return pxActor;
}

bool Asura_Cutscene_Handler::FindCutsceneActor_Callback(Asura_Hash_ID uHash, class Asura_Cutscene** ppxCutscene, void* pUserData)
{
	if (ppxCutscene && *ppxCutscene)
	{
		Asura_Cutscene_Actor* pxActor = (*ppxCutscene)->FindCutsceneActor(s_uFindCutsceneActorGuid);

		if (pxActor)
		{
			// Found it, set the user data and stop the search
			Asura_Cutscene_Actor** ppxActorOut = static_cast<Asura_Cutscene_Actor**>(pUserData);
			*ppxActorOut = pxActor;
			return false;
		}
	}

	// Continue while the actor remains unset.
	return true;
}

void Asura_Cutscene_Handler::ResetEnabledEventFlags()
{
	m_xCutsceneStorage.Traverse(ResetEnabledEventFlags_Callback);
}

void Asura_Cutscene_Handler::ResetEnabledEventFlags_Callback(Asura_Hash_ID uHash, class Asura_Cutscene** ppxCutscene)
{
	// ignore the hash, just use the data
	if (ppxCutscene && *ppxCutscene)
	{
		(*ppxCutscene)->EnableAllEvents();
	}
}

bool Asura_Cutscene_Handler::GetCanSkipCutscenes()
{
	// Only search the current cutscenes
	bool bCanSkip = true;
	m_xCurrentCutscenes.Traverse(GetCanSkipCutscenes_Callback, &bCanSkip);
	return bCanSkip;
}

bool Asura_Cutscene_Handler::GetCanSkipCutscenes_Callback(Asura_Hash_ID uHash, class Asura_Cutscene** ppxCutscene, void* pUserData)
{
	if (ppxCutscene && *ppxCutscene)
	{
		Asura_Cutscene* pxCutscene = *ppxCutscene;
		// Check if this is a cutscene we want to skip
		if (pxCutscene->ControlsCamera() && pxCutscene->IsPlaying()) // TODO - Is the IsPlaying check needed? This cutscene is in the current cutscenes list.
		{
			bool* pbCanSkip = static_cast<bool*>(pUserData);

			*pbCanSkip &= pxCutscene->IsSkippable() && (pxCutscene->Timer - pxCutscene->m_fCutsceneStartTime > Asura_Cutscene_System::s_fCutsceneSkipTime);

			// Continue unless we've found a cutscene that can't be skipped.
			return *pbCanSkip;
		}
	}

	return true;
}

void Asura_Cutscene_Handler::SkipCutscenes()
{
	// Only go through current cutscenes
	m_xCurrentCutscenes.Traverse(SkipCutscenes_Callback);
}

void Asura_Cutscene_Handler::SkipCutscenes_Callback(Asura_Hash_ID uHash, class Asura_Cutscene** ppxCutscene)
{
	if (ppxCutscene && *ppxCutscene)
	{
		Asura_Cutscene* pxCutscene = *ppxCutscene;
		// Check if this is a cutscene we want to skip
		if (pxCutscene->ControlsCamera() && pxCutscene->IsPlaying()) // TODO - Is the IsPlaying check needed? This cutscene is in the current cutscenes list.
		{
			pxCutscene->SkipToEndOfCutscene();
		}
	}
}

// Use the null/non-null state of the static function pointer to decide which traversal to do.
// Will just go straight through if both are null
void Asura_Cutscene_Handler::TraverseAllCutscenes()
{
	Asura_Assert(s_pfnGenericCutsceneCallbackWithoutData || s_pfnGenericCutsceneCallbackWithData, "No cutscene callback function set");

	if (s_pfnGenericCutsceneCallbackWithoutData)
	{
		m_xCutsceneStorage.Traverse(s_pfnGenericCutsceneCallbackWithoutData);
	}
	else if (s_pfnGenericCutsceneCallbackWithData)
	{
		m_xCutsceneStorage.Traverse(s_pfnGenericCutsceneCallbackWithData, s_pGenericCutsceneCallbackUserData);
	}
}

void Asura_Cutscene_Handler::TraverseCurrentCutscenes()
{
	Asura_Assert(s_pfnGenericCutsceneCallbackWithoutData || s_pfnGenericCutsceneCallbackWithData, "No cutscene callback function set");

	if (s_pfnGenericCutsceneCallbackWithoutData)
	{
		m_xCurrentCutscenes.Traverse(s_pfnGenericCutsceneCallbackWithoutData);
	}
	else if (s_pfnGenericCutsceneCallbackWithData)
	{
		m_xCurrentCutscenes.Traverse(s_pfnGenericCutsceneCallbackWithData, s_pGenericCutsceneCallbackUserData);
	}
}

// F U N C T I O N   I M P L E M E N T A T I O N S ///////////////////////////

#if defined(ASURA_SUPPORT_COMMAND_CONSOLE) && defined(ASURA_TOOLS)
static bool ProfileCutscene( const char* szCutsceneName, bool bInterrogate, bool bHelpRequested )
{
	if( bHelpRequested || bInterrogate )
	{
		Asura_CommandConsole::Printf( CONSOLECMD_PROFILECUTSCENE L" <cutscene name>" );
		Asura_CommandConsole::Printf( "    Plays the specified cutscene and provides an average FPS at the end." );
	}
	else
	{
		// Find specified cutscene. If we don't have it loaded locally then
		// we're not going to be able to play it anyway.
		Asura_Cutscene* pxCutscene = Asura_Cutscene_System::GetCutscene( szCutsceneName );
		if( pxCutscene )
		{
			// Get cutscene hash.
			Asura_Hash_ID uCutsceneHash = Asura_GetHashID( szCutsceneName );
			// Send a message to the server requesting this cutscene be
			// played.
			Asura_Message* pxMsg = Asura_Network::CreateMessage( ASURA_MSG_CUTSCENECONTROLLER_REQ_PROFILE_CUTSCENE, sizeof(Asura_Hash_ID) );
			if( pxMsg )
			{
				// Setup msg data.
				*(reinterpret_cast<Asura_Hash_ID*>(pxMsg->GetData())) = uCutsceneHash;

				// Setup other details.
				pxMsg->To   = ASURA_GUID_SERVER;
				pxMsg->From = ASURA_GUID_CLIENT;
	        
				// And send.
				Asura_Network::Send( pxMsg );
			}
		}
		else
		{
			Asura_CommandConsole::Printf( "Cutscene '%s' does not exist.", szCutsceneName );
			return false;
		}       
	}

	return true;
}
#endif // #if defined(ASURA_SUPPORT_COMMAND_CONSOLE) && defined(ASURA_TOOLS)

