//
// Asura_ServerEntity_SplitterBlock.cpp
//
// Created by Richard Rice - 18/09/2002
// 
// Copyright (C) 2002 Rebellion, All Rights Reserved.
//

#include "Core/Asura.h"

// This file's header.
#include "Entity/Triggers/Asura_ServerEntity_SplitterBlock.h"

// Engine independent.
#include "ChunkSystem/Asura_Chunk_Macros.h"
#include "ChunkSystem/Asura_Chunk_Stream.h"
#include "Core/Asura_Maths.h"
#include "Core/Asura_Timers.h"
#include "Debugging/Asura_Assert.h"
#include "Entity/Asura_Entity_Classification.h"
#include "Entity/Asura_StaticMessage_System.h"

#ifdef ASURA_SUPPORT_ENTITYWATCH
#include "Entity/Asura_EntityWatch_System.h"
#endif

#ifdef ASURA_TOOLS
#include "Entity/Tools_Message_Editing.h"
#include "EntityTemplate/Tools_EntityTemplate_System.h"
#endif

#define ASURA_SERVERENTITY_SPLITTERBLOCK_VERSION_NUMBER 6


//////////////////////////////////////////////////////////////////////////////////////////////////
/*---------------------------**
** Asura_SplitterBlockOutput **
**---------------------------*/

void Asura_SplitterBlockOutput::SetDefaultValues()
{
	m_usMessageBlock = ASURA_STATICMESSAGE_EMPTYBLOCK;
	m_usFlags = 0;
	m_bActive = 1;
	m_uWeight = 1;

	#ifdef ASURA_TOOLS
	m_xName.MakeBlank();
	#endif
}

//////////////////////////////////////////////////////////////////////////////////////////////////
/*----------------------------------**
** Asura_ServerEntity_SplitterBlock **
**----------------------------------*/

#ifdef ASURA_TOOLS
Asura_Hashed_List<Asura_ServerEntity_SplitterBlock> Asura_ServerEntity_SplitterBlock::s_xList(5);
#endif

Asura_ServerEntity_SplitterBlock::Asura_ServerEntity_SplitterBlock()
:Asura_ServerEntity_Activatable()
{
	#ifdef ASURA_TOOLS
	s_xList.AddToList(this);
	#endif

	Classification = AsuraEntityClass_SplitterBlock;
	
	m_uNumberOfOutputs = 0;
	m_pxOutputs = 0;
	m_fOutputMinDelayTime = 0.0f;
	m_fOutputMaxDelayTime = 0.0f;
	m_pfOutputTimers = NULL;
	m_uSplitterFlags = 0;
	m_uNextOutputToTrigger = 0;
}

Asura_ServerEntity_SplitterBlock::~Asura_ServerEntity_SplitterBlock()
{
	#ifdef ASURA_TOOLS
	s_xList.RemoveFromList(this);
	#endif
	delete [] m_pxOutputs;
	delete [] m_pfOutputTimers;
}

Asura_ServerEntity_SplitterBlock::Asura_ServerEntity_SplitterBlock(Asura_Guid uGuid)
:Asura_ServerEntity_Activatable(uGuid)
{
#ifdef ASURA_TOOLS
	s_xList.AddToList(this);
#endif

	Classification = AsuraEntityClass_SplitterBlock;

	m_uNumberOfOutputs = 0;
	m_pxOutputs = 0;
	m_fOutputMinDelayTime = 0.0f;
	m_fOutputMaxDelayTime = 0.0f;
	m_pfOutputTimers = NULL;
	m_uSplitterFlags = 0;
	m_uNextOutputToTrigger = 0;
}

void Asura_ServerEntity_SplitterBlock::ReadFromChunkStream(Asura_Chunk_Stream& xStream)
{
	// Unfortunately, this class doesn't use the best-practice approach of: our version -> parent write -> our data
	// This is because it's been converted from using chunkbuffer constructors.
	// Instead we have: parent write -> our version and data.
	PARENT::ReadFromChunkStream(xStream);

	int iVersion;
	xStream >> iVersion;
	Asura_Assert(iVersion>=1 && iVersion <= ASURA_SERVERENTITY_SPLITTERBLOCK_VERSION_NUMBER,"Unknown chunk version");

	xStream >> m_uSplitterFlags;
	if (iVersion <= 2)
	{
		// Make sure the unused splitter block flags are clear
		m_uSplitterFlags &= ~(Asura_SplitterBlockFlag_Random | Asura_SplitterBlockFlag_DeactivateOutputWhenUsed);
	}
	xStream >> m_uNumberOfOutputs;

	m_pxOutputs = NULL;
	m_pfOutputTimers = NULL;
	if (m_uNumberOfOutputs)
	{
		m_pxOutputs = new Asura_SplitterBlockOutput[m_uNumberOfOutputs];
		if (NeedsTimerArray())
		{
			m_pfOutputTimers = new float[m_uNumberOfOutputs];
		}
		else
		{
			// KOR - 23/03/3006 - update only needs to be called if we have output timers.
			SetNeverUpdates(true);
		}
		for(unsigned i=0;i<m_uNumberOfOutputs;i++)
		{
			xStream >> m_pxOutputs[i].m_usMessageBlock;
			xStream >> m_pxOutputs[i].m_usFlags;
			xStream >> m_pxOutputs[i].m_uWeight;
			#ifdef ASURA_TOOLS
			m_pxOutputs[i].m_xName.MakeBlank();
			#endif

			if(iVersion<2)
			{
				//reset the unused bits
				m_pxOutputs[i].m_usSpareBits = 0;
			}
			if (m_pfOutputTimers)
			{
				xStream >> m_pfOutputTimers[i];
			}
		}
	}
	
	if (iVersion > 2)
	{
		xStream >> m_fOutputMinDelayTime;
		xStream >> m_fOutputMaxDelayTime;
	}
	else
	{
		m_fOutputMinDelayTime = 0.0f;
		m_fOutputMaxDelayTime = 0.0f;
	}

	if(iVersion >= 4)
	{
		xStream >> m_uNextOutputToTrigger;
	}
	else
	{
		m_uNextOutputToTrigger = 0;
	}

	if(iVersion >= 5)
	{
		int iToolsDataSize;
		xStream >> iToolsDataSize;

		#ifdef ASURA_TOOLS
		if(iToolsDataSize)
		{
			for(u_int i=0;i<m_uNumberOfOutputs;i++)
			{
				xStream >> m_pxOutputs[i].m_xName;
			}
		}
		#else
		//not in tools , so just skip this
		xStream.Skip(iToolsDataSize);
		#endif // ASURA_TOOLS
	}

}

void Asura_ServerEntity_SplitterBlock::WriteToChunkStream(Asura_Chunk_Stream& xStream) const
{
	/*
	This entity type can be used in template files , so any changes to WriteToChunkStream may
	require ApplyChangesForTemplateInstance() to be updated as well.
	Please see the comment at the top of Tools/EntityTemplate/Tools_EntityTemplate_System.h before 
	making any changes.
	RAR 29/04/2004
	*/
	PARENT::WriteToChunkStream(xStream);

	int iVersion = ASURA_SERVERENTITY_SPLITTERBLOCK_VERSION_NUMBER;
	xStream << iVersion;

	// Bitmask
	xStream << m_uSplitterFlags;
	xStream << m_uNumberOfOutputs;
	for(unsigned i=0;i<m_uNumberOfOutputs;i++)
	{
		xStream << m_pxOutputs[i].m_usMessageBlock;
		// Bitfield
		Asura_WriteToStream_Bitfield_Wrapper<u_short> xBitfieldWrapper( m_pxOutputs[i].m_usFlags );
		xBitfieldWrapper.Add(m_pxOutputs[i].m_bActive, 1);
#ifdef ASURA_TOOLS
		xBitfieldWrapper.Add(m_pxOutputs[i].m_usSpareBits, 14);
		xBitfieldWrapper.Add(m_pxOutputs[i].m_bJunk, 1);
#else
		xBitfieldWrapper.Add(m_pxOutputs[i].m_usSpareBits, 15);
#endif
		xStream << xBitfieldWrapper;

		xStream << m_pxOutputs[i].m_uWeight;
		if (NeedsTimerArray())
		{
			Asura_Assert(m_pfOutputTimers,"");
			xStream << m_pfOutputTimers[i];

		}
	}
	xStream << m_fOutputMinDelayTime;
	xStream << m_fOutputMaxDelayTime;
	xStream << m_uNextOutputToTrigger;

#ifdef ASURA_TOOLS
	if(!Tools_Level_Export::IsExport())//exported levels won't want the tools data
	{
		//save out data only needed by tools
		int iToolsDataSize = 0;
		for(u_int i=0;i<m_uNumberOfOutputs;i++)
		{
			iToolsDataSize += m_pxOutputs[i].m_xName.GetSizeInInts() * 4;
		}
		xStream << iToolsDataSize;

		for(u_int i=0;i<m_uNumberOfOutputs;i++)
		{
			xStream << m_pxOutputs[i].m_xName;
		}
	}
	else
#endif
	{
		//Either not in tools , or doing a level export.
		//In either case , we don't want the tools data.
		int iToolsDataSize = 0;
		xStream << iToolsDataSize;
	}
}


int Asura_ServerEntity_SplitterBlock::GetChunkBufferSize()
{
#if 0
	int iChunkSize = Asura_ServerEntity_Activatable::GetChunkBufferSize();
	
	if (NeedsTimerArray())
	{
		iChunkSize += sizeof(float) * m_uNumberOfOutputs; // m_pfOutputTimers
	}
	iChunkSize += sizeof(int) //version
			   +  sizeof(unsigned int) //m_uSplitterFlags
			   +  sizeof(unsigned int) // m_uNumberOfOutputs
			   +  8*m_uNumberOfOutputs //(8 bytes saved from Asura_SplitterBlockOutput
			   +  sizeof(float) // m_fOutputMinDelayTime
			   +  sizeof(float) // m_fOutputMaxDelayTime
			   +  sizeof(unsigned int) //m_uNextOutputToTrigger
			   +  sizeof(int); //tools data size

	#ifdef ASURA_TOOLS
	if(!Tools_Level_Export::IsExport())//exported levels won't want the tools data
	{
		iChunkSize += GetToolsDataSize();
	}
	#endif

	return iChunkSize;
#else
	Asura_Assert(false, "CHUNK ERROR: deprecated function");
	return 0;
#endif
}

void Asura_ServerEntity_SplitterBlock::FillInChunkBuffer(char*& pcChunkBuffer)
{
#if 0
	/*
	This entity type can be used in template files , so any changes to FillInChunkBuffer may
	require ApplyChangesForTemplateInstance() to be updated as well.
	Please see the comment at the top of Tools/EntityTemplate/Tools_EntityTemplate_System.h before 
	making any changes.
	RAR 29/04/2004
	*/
	Asura_ServerEntity_Activatable::FillInChunkBuffer(pcChunkBuffer);

	int iVersion = ASURA_SERVERENTITY_SPLITTERBLOCK_VERSION_NUMBER;
	ASURA_CHUNK_FILLANDVERIFY( pcChunkBuffer, iVersion,int);

	// Bitmask
	ASURA_CHUNK_FILLANDVERIFY( pcChunkBuffer, m_uSplitterFlags,unsigned int);
	ASURA_CHUNK_FILLANDVERIFY( pcChunkBuffer, m_uNumberOfOutputs,unsigned int);
	for(unsigned i=0;i<m_uNumberOfOutputs;i++)
	{
		ASURA_CHUNK_FILLANDVERIFY( pcChunkBuffer, m_pxOutputs[i].m_usMessageBlock, unsigned short);
		// Bitfield
		ASURA_CHUNK_BITFIELD_FILLANDVERIFY( pcChunkBuffer, m_pxOutputs[i].m_usFlags, unsigned short);
		ASURA_CHUNK_FILLANDVERIFY( pcChunkBuffer, m_pxOutputs[i].m_uWeight, unsigned int);
		if (NeedsTimerArray())
		{
			Asura_Assert(m_pfOutputTimers,"");
			ASURA_CHUNK_FILLANDVERIFY( pcChunkBuffer, m_pfOutputTimers[i], float );
			
		}
	}
	ASURA_CHUNK_FILLANDVERIFY( pcChunkBuffer, m_fOutputMinDelayTime, float);
	ASURA_CHUNK_FILLANDVERIFY( pcChunkBuffer, m_fOutputMaxDelayTime, float);
	ASURA_CHUNK_FILLANDVERIFY( pcChunkBuffer, m_uNextOutputToTrigger, unsigned int);

	#ifdef ASURA_TOOLS
	if(!Tools_Level_Export::IsExport())//exported levels won't want the tools data
	{
		//save out data only needed by tools
		int iToolsDataSize = GetToolsDataSize();

		ASURA_CHUNK_FILLANDVERIFY( pcChunkBuffer, iToolsDataSize,int);

		for(u_int i=0;i<m_uNumberOfOutputs;i++)
		{
			ASURA_CHUNK_FILL_STRING(pcChunkBuffer,m_pxOutputs[i].m_xName);
		}
	}
	else
	#endif
	{
		//Either not in tools , or doing a level export.
		//In either case , we don't want the tools data.
		int iToolsDataSize = 0;
		ASURA_CHUNK_FILLANDVERIFY( pcChunkBuffer, iToolsDataSize,int);
	}

#else
	Asura_Assert(false, "CHUNK ERROR: deprecated function");
#endif
}

#ifdef ASURA_TOOLS
int Asura_ServerEntity_SplitterBlock::GetToolsDataSize()
{
#if 0
	int iChunkSize = 0;
	for(u_int i=0;i<m_uNumberOfOutputs;i++)
	{
		iChunkSize += m_pxOutputs[i].m_xName.GetSizeInInts()*4;
	}
	return iChunkSize;
#else
	Asura_Assert(false, "CHUNK ERROR: deprecated function");
	return 0;
#endif
}
#endif

void Asura_ServerEntity_SplitterBlock::Update()
{
	if (m_pfOutputTimers && IsActive())
	{
		for(unsigned i=0;i<m_uNumberOfOutputs;i++)
		{
			if (m_pfOutputTimers[i] > 0.0f)
			{
				m_pfOutputTimers[i] -= Asura_Timers::GetGameTimeSlice();
				if (m_pfOutputTimers[i] < 0.0f)
				{
					m_pfOutputTimers[i] = 0.0f;
					TriggerOutput(i, false);
				}
			}
		}
	}
}


bool Asura_ServerEntity_SplitterBlock::HandleMessage(const Asura_Message* pxMessage)
{
	if (!pxMessage) return false;

	switch(pxMessage->GetMsgID())
	{
		//these use the same message structure  so deal with them at the same time
		case ASURA_MSG_SPLITTER_ACTIVATEOUTPUT :
		case ASURA_MSG_SPLITTER_DEACTIVATEOUTPUT :
		case ASURA_MSG_SPLITTER_TRIGGEROUTPUT :
		{
			const Asura_Message_SplitterBlock_Output* pxMessageData = reinterpret_cast
				<const Asura_Message_SplitterBlock_Output*>(pxMessage->GetData());
			if(pxMessageData->m_uOutputIndex<m_uNumberOfOutputs)
			{
				if(pxMessage->GetMsgID() == ASURA_MSG_SPLITTER_TRIGGEROUTPUT)
				{
					if(IsActive() && m_pxOutputs[pxMessageData->m_uOutputIndex].m_bActive)
					{
						TriggerOutput(pxMessageData->m_uOutputIndex, (m_uSplitterFlags & Asura_SplitterBlockFlag_UseRandomDelay) != 0);
					}
				}
				else
				{
					m_pxOutputs[pxMessageData->m_uOutputIndex].m_bActive = (pxMessage->GetMsgID()==ASURA_MSG_SPLITTER_ACTIVATEOUTPUT);
					if(m_pfOutputTimers && !m_pxOutputs[pxMessageData->m_uOutputIndex].m_bActive)
					{
						//Output has been deactivated , stop its timer as well. RAR 25/05/2006
						m_pfOutputTimers[pxMessageData->m_uOutputIndex] = 0;
					}
				}
			}
			else
			{
				Asura_Assert(pxMessageData->m_uOutputIndex<m_uNumberOfOutputs,"Output index to activate/deactivate is too large");
			}
			return true;
		}
		
		default :
			break;
	}

	// Pass to parent.
	return Asura_ServerEntity_Activatable::HandleMessage(pxMessage);
}


void Asura_ServerEntity_SplitterBlock::OnTrigger()
{
	if(m_uSplitterFlags & Asura_SplitterBlockFlag_CycleOutputs)
	{
		for(u_int uChecked=0;uChecked<m_uNumberOfOutputs;uChecked++,m_uNextOutputToTrigger++)
		{
			if(m_uNextOutputToTrigger>=m_uNumberOfOutputs) m_uNextOutputToTrigger=0;
			
			//is this output active?
			if(m_pxOutputs[m_uNextOutputToTrigger].m_bActive)
			{
				TriggerOutput(m_uNextOutputToTrigger,(m_uSplitterFlags & Asura_SplitterBlockFlag_UseRandomDelay) != 0);
				m_uNextOutputToTrigger++;
				break;
			}			
		}
	}
	else if(m_uSplitterFlags & Asura_SplitterBlockFlag_Random)
	{
		//get the total weighting of all active outputs
		unsigned uTotalWeight = 0;
		for(unsigned i=0;i<m_uNumberOfOutputs;i++)
		{
			if(m_pxOutputs[i].m_bActive)
			{
				uTotalWeight += m_pxOutputs[i].m_uWeight;
			}
		}
		if(!uTotalWeight) return;//no active outputs with valid weightings
	
		//get a random number from 0 to uTotalWeight-1
		unsigned uRandom = (Asura_Random::GetIntU())%uTotalWeight;

		//find out which one was chosen
		for(unsigned i=0;i<m_uNumberOfOutputs;i++)
		{
			if(m_pxOutputs[i].m_bActive)
			{
				if(uRandom<m_pxOutputs[i].m_uWeight)
				{
					TriggerOutput(i, (m_uSplitterFlags & Asura_SplitterBlockFlag_UseRandomDelay) != 0);
					break;
				}
				uRandom -= m_pxOutputs[i].m_uWeight;
			}
		}
	
	}
	else
	{
		//send the messages from all the active outputs
		for(unsigned i=0;i<m_uNumberOfOutputs;i++)
		{
			if(m_pxOutputs[i].m_bActive)
			{
				TriggerOutput(i, (m_uSplitterFlags & Asura_SplitterBlockFlag_UseRandomDelay) != 0);
			}
		}
	}
}

void Asura_ServerEntity_SplitterBlock::OnDeactivation()
{
	//If there are any delayed messages counting down ,then cancel them now that we are deactivated. RAR 25/05/2006
	if(m_pfOutputTimers)
	{
		for(unsigned i=0;i<m_uNumberOfOutputs;i++)
		{
			m_pfOutputTimers[i] = 0.0f;
		}
	}

	Asura_ServerEntity_Activatable::OnDeactivation();
}


void Asura_ServerEntity_SplitterBlock::TriggerOutput(int iIndex, bool bDelayOutput)
{
	float fDelay = 0.0f;
	if(bDelayOutput)
	{
		fDelay = Asura_Random::GetFloat(m_fOutputMinDelayTime, m_fOutputMaxDelayTime);
	}

	if(fDelay && !(m_uSplitterFlags & Asura_SplitterBlockFlag_SendRandomDelayImmediately))
	{
		Asura_Assert(m_pfOutputTimers,"");
		//update output timer with new delay
		if (m_pfOutputTimers[iIndex] > 0.0f)
		{
			m_pfOutputTimers[iIndex] = Asura_Maths::Min(fDelay, m_pfOutputTimers[iIndex]);
		}
		else
		{
			m_pfOutputTimers[iIndex] = fDelay;
		}
	}
	else
	{
		//Post the messages now (possibly with a delay)
		Asura_StaticMessage_System::SendMessages(m_pxOutputs[iIndex].m_usMessageBlock,Guid,fDelay);
		if(m_uSplitterFlags & Asura_SplitterBlockFlag_DeactivateOutputWhenUsed)
		{
			m_pxOutputs[iIndex].m_bActive = false;
		}
	}
}

#ifdef ASURA_TOOLS
void Asura_ServerEntity_SplitterBlock::UpdateOutputTimerArray()
{

	if (m_pfOutputTimers)
	{
		delete [] m_pfOutputTimers;
		m_pfOutputTimers = NULL;

	}
	if (m_uNumberOfOutputs && NeedsTimerArray())
	{
		m_pfOutputTimers = new float[m_uNumberOfOutputs];
		for (u_int i=0;i<m_uNumberOfOutputs;i++)
		{
			m_pfOutputTimers[i] = 0.0f;
		}
	}

	//Update only needs to be called if there are some output timers
	SetNeverUpdates(!m_pfOutputTimers);
}
#endif // ASURA_TOOLS


void Asura_ServerEntity_SplitterBlock::CreateFromChunkStream(Asura_Guid uGuid, Asura_Chunk_Stream& xStream)
{
	Asura_ServerEntity_SplitterBlock* pxEntity = new Asura_ServerEntity_SplitterBlock(uGuid);
	if (pxEntity)
	{
		pxEntity->ReadFromChunkStream(xStream);
	}
}

#ifdef ASURA_TOOLS
void Asura_ServerEntity_SplitterBlock::GetCustomEventName(int iEventIndex, Asura_String& xEventName)
{
	xEventName = "";
}
#endif

#ifdef ASURA_TOOLS
void Asura_ServerEntity_SplitterBlock::ApplyChangesForTemplateInstance(Tools_EntityTemplateInstance* pxInstance)
{
	Asura_ServerEntity_Activatable::ApplyChangesForTemplateInstance(pxInstance);

	//update the message blocks
	for(u_int i=0;i<m_uNumberOfOutputs;i++)
	{
		m_pxOutputs[i].m_usMessageBlock = pxInstance->ConvertMessageBlock(m_pxOutputs[i].m_usMessageBlock);
	}

}
#endif // ASURA_TOOLS


#ifdef ASURA_SUPPORT_ENTITYWATCH
void Asura_ServerEntity_SplitterBlock::DisplayEntityWatchText()
{
	Asura_ServerEntity_Activatable::DisplayEntityWatchText();
		
	Asura_EntityWatch_System::NextLine();
	Asura_EntityWatch_System::PrintText(0,"Outputs :");
	for(u_int i=0;i<m_uNumberOfOutputs;i++)
	{
		Asura_EntityWatch_System::PrintText(3,m_pxOutputs[i].m_bActive ? "On" : "Off");	
	}
	
	Asura_EntityWatch_System::NextLine();
	if(m_uSplitterFlags & Asura_SplitterBlockFlag_CycleOutputs)
	{
		Asura_EntityWatch_System::PrintText(0,"Next Output : %d",m_uNextOutputToTrigger % m_uNumberOfOutputs);	
	}
}
#endif

#ifdef ASURA_TOOLS
bool Asura_ServerEntity_SplitterBlock::GetMessageDescription(const Asura_StaticMessage* pxMessage,Asura_String& xOutputDesc)
{
	if(pxMessage->m_usMsgID != ASURA_MSG_SPLITTER_ACTIVATEOUTPUT &&
		pxMessage->m_usMsgID != ASURA_MSG_SPLITTER_DEACTIVATEOUTPUT &&
		pxMessage->m_usMsgID != ASURA_MSG_SPLITTER_TRIGGEROUTPUT) return false;

	const Asura_Message_SplitterBlock_Output* pxData = (const Asura_Message_SplitterBlock_Output*) pxMessage->m_pData;


	//Find the entity being triggered
	Asura_ServerEntity_SplitterBlock* pxEntity = FindEntity(Tools_Message_Editing::GetFinalDestinationGuid(pxMessage));
	if(!pxEntity) return false;

	char szText[100];
	//get the name of the output
	const char* pszOutputName = 0;
	if(pxData->m_uOutputIndex < pxEntity->m_uNumberOfOutputs)
	{
		if(!pxEntity->m_pxOutputs[pxData->m_uOutputIndex].m_xName.IsBlank())
		{
			pszOutputName = pxEntity->m_pxOutputs[pxData->m_uOutputIndex].m_xName.GetString();
		}
	}
	
	const char* szMessageName = 0;
	switch(pxMessage->m_usMsgID)
	{
		case ASURA_MSG_SPLITTER_ACTIVATEOUTPUT :
			szMessageName = "Activate Output";
			break;

		case ASURA_MSG_SPLITTER_DEACTIVATEOUTPUT :
			szMessageName = "Deactivate Output";
			break;

		case ASURA_MSG_SPLITTER_TRIGGEROUTPUT :
			szMessageName = "Trigger Output";
			break;
		default :
			Asura_Assert(0,"Unexpected message type");
			break;

	}
	Asura_PrintfToBfr(szText,"%s %d%s%s%s",100,true,szMessageName,pxData->m_uOutputIndex,pszOutputName ? " (" : "",pszOutputName ? pszOutputName : "",pszOutputName ? ")" : "");
	xOutputDesc = szText;

	return true;
}
#endif //ASURA_TOOLS
