////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

//
// Tools_MessageEditor.cpp
//
// Created by Dan Clarke - 28/10/2008
// 
// Copyright (C) 2005 Rebellion, All Rights Reserved.
//

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

#include "Core/Asura.h"

#ifdef ASURA_TOOLS

#include "Entity/Tools_MessageEditor.h"
#include "Core/Asura_UserInput.h"
#include "Core/Asura_SortAndSearch.h"
#include "Entity/Asura_Entity.h"
#include "Entity/Asura_Entity_Event.h"
#include "Entity/Asura_StaticMessage_System.h"
#include "Entity/Triggers/Asura_ServerEntity_ProxyTrigger.h"
#include "Entity/Tools_Entity_PhysicalObject.h"
#include "Interface/Asura_Cursor.h"
#include "Interface/Asura_Overlay_System.h"
#include "Interface/Asura_Overlay.h"
#include "Interface/Asura_Dialog.h"
#include "Interface/Asura_Table_Dialog.h"
#include "Rendering/Asura_Render.h"

#include "Tools_Overlays.h"
#include "Entity/Triggers/Asura_ServerEntity_Trigger.h"
#include "Entity/Triggers/Asura_ServerEntity_SplitterBlock.h"
#include "Entity/Triggers/Asura_ServerEntity_TimelineTrigger.h"
#include "Entity/Tools_Entity_SplitterBlock_Object.h"
#include "Entity/Tools_Entity_TimelineTrigger_Object.h"
#include "Entity/Tools_Entity_Trigger_Object.h"
#include "Entity/Tools_SecondaryEntityFile_System.h"
#include "EntityTemplate/Tools_Entity_Template_Object.h"
#include "Load&Save/Tools_SaveAsura.h"
#include "Misc/Tools_Timed_Messages.h"
#include "Objects/Tools_Object_System.h"
#include "Objects/Tools_SpecialGuid_Object.h"
#include "Rendering/Asura_Text_Output.h"
#include "Sound/Tools_DialogueEvent_Overlay.h"

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

#define MESSAGE_EDITOR_TABLE_WIDTH			(80)
#define MESSAGE_EDITOR_TABLE_HEIGHT			(18)
#define MESSAGE_EDITOR_TABLE_ROW_HEIGHT		(0.5f)
#define DEFAULT_TABCONTROL_HEIGHT			(10.0f)
#define BUTTON_SECTION_HEIGHT				(4.0f)

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

const char* Tools_MessageEditor::s_aszTabNames[NUM_TABS] =
{
	"Outgoing Messages",
	"Incoming Messages",
};

const char* Tools_MessageEditor::s_aszColumnNames[NUM_COLUMN_NAMES] =
{
	"Delay",
	"Message",
	"Target",
	"Prob.",
	"Disable",
};

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

struct ToDeleteStruct
{
	u_int uRowIndex;
	u_short* pusBlockId;
	u_short usBlockId2;
	Asura_StaticMessageSetIndex eSet;
	int iEventId;
	int iMessageIndex;
	class Tools_Entity_SplitterBlock_Object* pxSplitterBlockObject;
	class Asura_ServerEntity_TimelineTrigger* pxTimelineTrigger;
	int iBlockIndex;
};

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

struct Tools_RowSortInfo
{
	int iIndex;
	float fDelay;
};

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

class Tools_ChooseAddMessageTargets : public Tools_ChooseObject
{
	public :
		Tools_ChooseAddMessageTargets(Tools_Object* pxSourceObject, Asura_MessageTable_Dialog* pxTableDialog)
			: m_pxSourceObject(pxSourceObject), m_pxMessageTable(pxTableDialog)
		{}

		virtual bool CanBeSelected(Tools_Object* pxObject);
		virtual void ObjectSelected(Tools_Object* pxObject);
		virtual void OnFinish ();
		virtual bool SupportMultipleSelection() {return true;}	

	private:
		Tools_Object* m_pxSourceObject;
		Asura_MessageTable_Dialog* m_pxMessageTable;
};

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

class Tools_ChooseMessageTargetObject : public Tools_ChooseObject
{
	public :
		Tools_ChooseMessageTargetObject(Tools_Object* pxSourceObject, Asura_MessageTable_Dialog* pxTableDialog)
			: m_pxSourceObject(pxSourceObject), m_pxMessageTable(pxTableDialog)
		{}

		virtual bool CanBeSelected(Tools_Object* pxObject);
		virtual void ObjectSelected(Tools_Object* pxObject);
		virtual void OnStart (){ s_bInRetargetMode = true; }
		virtual void OnFinish (){ delete this; s_bInRetargetMode = false; }

		static bool IsInRetargetMode() { return s_bInRetargetMode; }

	private:
		Tools_Object* m_pxSourceObject;
		Asura_MessageTable_Dialog* m_pxMessageTable;
		static bool s_bInRetargetMode;
};

bool Tools_ChooseMessageTargetObject::s_bInRetargetMode = false;

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

class Tools_MessageEditor_Overlay : public Asura_Overlay
{
	public:
		Tools_MessageEditor_Overlay(const char *szName, bool bAlwaysOnTop, Asura_Vector_2* pxPosition = 0) : Asura_Overlay(szName, bAlwaysOnTop, pxPosition) {}

		void PerFrameUpdate(void)
		{
			//if(IsMouseOver())
			if(Asura_Overlay_System::WasLastMouseClickOnOverlay() && (Asura_Overlay_System::GetTopOverlayID() == TOOLS_OVERLAY_MESSAGE_EDITOR))
			{
				//make sure no gadget is taking keyboard input.
				if(!Asura_Overlay_System::GetFocusGadget())
				{
					Tools_MessageEditor::ProcessKeyboardShortcuts();
				}
			}

			Asura_Overlay::PerFrameUpdate();
		}

		bool DoesOverlayOverrideCutAndPasteKey()
		{
			//return IsMouseOver();
			return true;
		}

		bool DoesOverlayOverrideDeleteKey()
		{
			//return IsMouseOver();
			return true;
		}

	private:
		bool IsMouseOver()
		{
			float fMouseX, fMouseY;
			Asura_Cursor::GetScreenCoords(fMouseX, fMouseY);

			fMouseX -= m_xOrigin.x;
			fMouseY -= m_xOrigin.y;

			if((fMouseX >= 0.0f) && (fMouseX < m_fWidth))
			{
				if((fMouseY >= 0.0f) && (fMouseY < m_fHeight))
				{
					return true;
				}
			}

			return false;
		}
};

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

void Tools_MessageEditor_Clipboard::Clear()
{
	if(m_pxMessages)
	{
		for(u_int n = 0; n < m_uNumMessagesInClipboard; n++)
		{
			m_pxMessages[n].xMessage.DeallocateMessageData();
		}

		delete [] m_pxMessages;
		m_pxMessages = NULL;
	}

	m_uNumMessagesInClipboard = 0;
	m_uAllocatedArraySize = 0;
}

void Tools_MessageEditor_Clipboard::Add(Asura_StaticMessage* pxMessage, u_short usSourceBlockId)
{
	if((m_uNumMessagesInClipboard + 1) > m_uAllocatedArraySize)
	{
		Tools_MessageEditor_ClipboardItem* pxOldMessageArray = m_pxMessages;

		m_uAllocatedArraySize += 5;

		m_pxMessages = new Tools_MessageEditor_ClipboardItem[m_uAllocatedArraySize];

		if(pxOldMessageArray)
		{
			Asura_Mem::Copy(m_pxMessages, pxOldMessageArray, sizeof(Tools_MessageEditor_ClipboardItem) * m_uNumMessagesInClipboard);

			delete [] pxOldMessageArray;
		}
	}

	m_pxMessages[m_uNumMessagesInClipboard].xMessage.Copy(pxMessage);
	m_pxMessages[m_uNumMessagesInClipboard].usSourceBlockId = usSourceBlockId;

	m_uNumMessagesInClipboard++;
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

bool Tools_MessageEditor::s_bIsOverlayVisible = false;
bool Tools_MessageEditor::s_bForceRecreateTable = false;
Asura_Overlay* Tools_MessageEditor::s_pxOverlay = NULL;
Asura_Vector_2 Tools_MessageEditor::s_xOverlayPosition(1.0f, 1.0f);
Asura_Vector_2 Tools_MessageEditor::s_xMessageAttributeOverlayPosition(25.0f, 13.0f);
Asura_Tabs_Dialog* Tools_MessageEditor::s_pxTabDialog = NULL;
Asura_MessageTable_Dialog* Tools_MessageEditor::s_paxMessageTables[NUM_TABS] = {NULL};
Tools_Object* Tools_MessageEditor::s_pxReferenceObject = NULL;
u_int Tools_MessageEditor::s_uSelectedTab = 0;
bool Tools_MessageEditor::s_bInMessageEditingMode = false;
Asura_StaticMessage* Tools_MessageEditor::s_pxMessageOverlayToOpen = NULL;
u_short* Tools_MessageEditor::s_pusMessageBlockToSelect = NULL;
bool Tools_MessageEditor::s_bBanDelayReordering = false;
Tools_Entity_Object* Tools_MessageEditor::s_pxReferenceToolsObject = NULL;
Asura_Entity_Server* Tools_MessageEditor::s_pxReferenceEntity = NULL;

Asura_StaticMessage* Tools_MessageEditor::s_pxMessageEditedByAttributeOverlay;
Asura_StaticMessage Tools_MessageEditor::s_xMessageBackup;

Asura_Function_Dialog* Tools_MessageEditor::s_pxAddMessageButton = NULL;
Asura_Function_Dialog* Tools_MessageEditor::s_pxRetargetMessageButton = NULL;
Asura_Function_Dialog* Tools_MessageEditor::s_pxGotoTargetButton = NULL;
Asura_Function_Dialog* Tools_MessageEditor::s_pxSelectTargetButton = NULL;
Asura_Function_Dialog* Tools_MessageEditor::s_pxCreateBlockButton = NULL;
Asura_Function_Dialog* Tools_MessageEditor::s_pxRenameButton = NULL;
Asura_Function_Dialog* Tools_MessageEditor::s_pxDeleteMessageButton = NULL;
Asura_Function_Dialog* Tools_MessageEditor::s_pxCopyMessageButton = NULL;
Asura_Function_Dialog* Tools_MessageEditor::s_pxPasteMessageButton = NULL;
Asura_Function_Dialog* Tools_MessageEditor::s_pxCollapseAllButton = NULL;
Asura_Function_Dialog* Tools_MessageEditor::s_pxExpandAllButton = NULL;

Tools_MessageEditor_Clipboard Tools_MessageEditor::xClipboard;

Asura_Vector_2 Tools_MessageEditor::s_xDialogSize(0.0f, 0.0f);
u_short* Tools_MessageEditor::s_pusCachedBlockIdFromRowSelection = NULL;

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

void Tools_MessageEditor::ToggleOverlay()
{
	if(s_bIsOverlayVisible)
	{
		CloseOverlay();
	}
	else
	{
		CreateOverlay();
	}
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

void Tools_MessageEditor::CreateOverlay()
{
	Asura_Assert(!s_bIsOverlayVisible, "Overlay is already visible!");

	Asura_Vector_2 xPosition(1.0f, 2.0f);

	// Create the overlay

	s_pxOverlay = new Tools_MessageEditor_Overlay("Message Editor", false, &s_xOverlayPosition);
	s_pxOverlay->m_uID = TOOLS_OVERLAY_MESSAGE_EDITOR;

	// Create the tab control

	s_pxTabDialog = new Asura_Tabs_Dialog(xPosition, s_aszTabNames, NUM_TABS);

	s_pxTabDialog->m_xMaxCoord.y = xPosition.y + DEFAULT_TABCONTROL_HEIGHT;

	s_pxOverlay->AddDialog(s_pxTabDialog);

	// Create the tab pages

	xPosition.y += 2.0f;

	CreateOutgoingMessagesTabPage(xPosition);
	CreateIncomingMessagesTabPage(xPosition);

	SelectTab(TAB_OUTGOING_MESSAGES);

	Update();
		

	// Submit the overlay

	Asura_Overlay_System::SubmitNewOverlay(s_pxOverlay);

	s_bIsOverlayVisible = true;
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

void Tools_MessageEditor::CloseOverlay()
{
	Asura_Overlay_System::CloseOverlays(TOOLS_OVERLAY_MESSAGE_EDITOR);
	CloseMessageAttributeOverlay();

	s_pxOverlay = NULL;
	s_pxTabDialog = NULL;
	s_paxMessageTables[TAB_OUTGOING_MESSAGES] = NULL;
	s_paxMessageTables[TAB_INCOMING_MESSAGES] = NULL;

	s_bIsOverlayVisible = false;
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

void Tools_MessageEditor::CloseMessageAttributeOverlay()
{
	Asura_Overlay_System::CloseOverlays(TOOLS_OVERLAY_MESSAGEEDIT);

	#ifdef ASURA_USE_AXON
	Tools_Message_Editing::DestroyGS2LinkWeightStrings();
	#endif

	s_pxMessageEditedByAttributeOverlay = 0;
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

void Tools_MessageEditor::Shutdown()
{
	CloseOverlay();
	s_xMessageBackup.DeallocateMessageData();
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

void Tools_MessageEditor::ProcessKeyboardShortcuts()
{
	if(Asura_UserInput::KeyPressed(ASURA_KEYID_CTRL))
	{
		//if(s_pxTabDialog->GetSelected() == TAB_OUTGOING_MESSAGES)
		{
			if(Asura_UserInput::DebouncedKeyPressed('c'))
			{
				CopyToClipboard();
			}

			if(Asura_UserInput::DebouncedKeyPressed('x'))
			{
				CutToClipboard();
			}

			if(Asura_UserInput::DebouncedKeyPressed('v'))
			{
				PasteFromClipboard();
			}
		}
	}
	else if(!Asura_UserInput::KeyPressed(ASURA_KEYID_SHIFT) && !Asura_UserInput::KeyPressed(ASURA_KEYID_ALT))
	{
		if(s_pxTabDialog->GetSelected() == TAB_OUTGOING_MESSAGES)
		{
			if(Asura_UserInput::DebouncedKeyPressed(ASURA_KEYID_DEL))
			{
				CloseMessageAttributeOverlay();
				DeleteMessage();
			}
		}
	}
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

void Tools_MessageEditor::Update()
{
	// Check if the overlay has been closed by the X button on the overlay

	if(s_bIsOverlayVisible && !Asura_Overlay_System::DoesOverlayExist (TOOLS_OVERLAY_MESSAGE_EDITOR))
	{
		CloseOverlay();
	}

	if(s_pxOverlay && !s_pxOverlay->IsHidden())
	{
		if(((s_pxReferenceObject != Tools_Object_System::SelectedObject) && !s_bInMessageEditingMode) || (s_uSelectedTab != s_pxTabDialog->GetSelected()) || s_bForceRecreateTable || s_paxMessageTables[s_uSelectedTab]->IsTableRecreateRequested(s_bBanDelayReordering))
		{
			// Current selected object has changed or the current tab has changed, so update the overlay

			s_bForceRecreateTable = false;
			s_paxMessageTables[s_uSelectedTab]->ResetTableRecreateRequest();

			if(!s_bInMessageEditingMode)
			{
				SetReferenceObject(Tools_Object_System::SelectedObject);
			}

			s_uSelectedTab = s_pxTabDialog->GetSelected();

			// Kill the old message editing overlay
			CloseMessageAttributeOverlay();

			RecreateTable();

			if(s_pxMessageOverlayToOpen)
			{
				CreateMessageAttributesOverlay(s_pxMessageOverlayToOpen);

				s_pxMessageOverlayToOpen = NULL;
			}

			for(u_int n = 0; n < NUM_TABS; n++)
			{
				s_paxMessageTables[n]->SetTableVisible(false);
			}

			s_paxMessageTables[s_uSelectedTab]->SetTableVisible(true);

			s_bBanDelayReordering = false;

			if(s_pxReferenceObject)
			{
				Asura_String xTitle = Asura_String("Message Editor - \"") + s_pxReferenceObject->GetName() + "\"";

				s_pxOverlay->SetTitle(xTitle.GetString());
			}
			else
			{
				s_pxOverlay->SetTitle("Message Editor");
			}
		}

		if(s_bInMessageEditingMode || Tools_ChooseMessageTargetObject::IsInRetargetMode())
		{
			Asura_Overlay* pxEntityManagerOverlay = Asura_Overlay_System::GetOverlay(TOOLS_OVERLAY_SECONDARYENTFILE);
			if(pxEntityManagerOverlay)
			{
				pxEntityManagerOverlay->SetOverrideOverlaysHiddenFlag(true);
			}

			if(s_pxOverlay)
			{
				s_pxOverlay->SetOverrideOverlaysHiddenFlag(true);
			}
		}

		// Keyboard shortcuts

		if(		!Asura_UserInput::KeyPressed(ASURA_KEYID_CTRL)
			&&	!Asura_UserInput::KeyPressed(ASURA_KEYID_SHIFT)
			&&	!Asura_UserInput::KeyPressed(ASURA_KEYID_ALT))
		{
			if(Asura_UserInput::DebouncedKeyPressed('g'))
			{
				switch(s_pxTabDialog->GetSelected())
				{
					case TAB_OUTGOING_MESSAGES:		GotoTarget();		break;
					case TAB_INCOMING_MESSAGES:		GotoSource();		break;
				}
			}

			if(Asura_UserInput::DebouncedKeyPressed('r'))
			{
				switch(s_pxTabDialog->GetSelected())
				{
					case TAB_OUTGOING_MESSAGES:		ChangeMessageTarget();		break;
				}
			}
		}

		if(Asura_UserInput::KeyPressed(ASURA_KEYID_SHIFT))
		{
			if(Asura_UserInput::DebouncedKeyPressed('.'))
			{
				switch(s_pxTabDialog->GetSelected())
				{
					case TAB_OUTGOING_MESSAGES:		SelectTarget();		break;
					case TAB_INCOMING_MESSAGES:		SelectSource();		break;
				}
			}
		}

		// Update the table size

		if(s_paxMessageTables[TAB_OUTGOING_MESSAGES])
		{
			s_paxMessageTables[TAB_OUTGOING_MESSAGES]->m_xMaxCoord.y = s_pxTabDialog->m_xMaxCoord.y;
		}

		if(s_paxMessageTables[TAB_INCOMING_MESSAGES])
		{
			s_paxMessageTables[TAB_INCOMING_MESSAGES]->m_xMaxCoord.y = s_pxTabDialog->m_xMaxCoord.y;
		}

		if(Asura_Overlay_System::DoesOverlayExist(TOOLS_OVERLAY_MESSAGEEDIT) && s_pxMessageEditedByAttributeOverlay)
		{
			//Check for any changes to the current message.
			//Being done like this , since it prevents having to update all the message editing code for all the messages
			//(including all the project ones) to get them to set the modified flag when they do anything.
			//RAR 06/04/2009
			if(s_xMessageBackup != *s_pxMessageEditedByAttributeOverlay)
			{
				SetEditedMessageModified();
				s_xMessageBackup.Copy(s_pxMessageEditedByAttributeOverlay);
			}
		}
		else
		{
			s_pxMessageEditedByAttributeOverlay = 0;	
		}
	}
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

static int SortStaticMessages(const void* pData1, const void* pData2)
{
	const Tools_RowSortInfo* pxData1 = (const Tools_RowSortInfo*) pData1;
	const Tools_RowSortInfo* pxData2 = (const Tools_RowSortInfo*) pData2;

	//sort by delay, using the original index to resolve tie breaks
	if(pxData1->fDelay < pxData2->fDelay) return -1;
	if(pxData1->fDelay > pxData2->fDelay) return 1;
	if(pxData1->iIndex < pxData2->iIndex) return -1;
	if(pxData1->iIndex > pxData2->iIndex) return 1;
	return 0;
}

void Tools_MessageEditor::SortMessages(unsigned short usMessageBlock)
{
	if(s_bBanDelayReordering)
	{
		return;
	}

	/*
	int iNumMessagesInBlock = Asura_StaticMessage_System::GetNumberOfMessagesInBlock(m_usMessageBlock);

	Tools_RowSortInfo *pafSortArray = new Tools_RowSortInfo[iNumMessagesInBlock];

	// Copy messages to sort array

	for(int i = 0; i < iNumMessagesInBlock; i++)
	{
		Asura_StaticMessage* pxMessage = Asura_StaticMessage_System::GetMessagePointer(m_usMessageBlock, i);
		pafSortArray[i].iIndex = i;
		pafSortArray[i].fDelay = pxMessage->m_fDelay;
	}

	// Sort

	Asura_SortAndSearch::Quicksort(pafSortArray, iNumMessagesInBlock, SortRowsFunc);

	// Swap the messages around based on the sort array

	delete [] pafSortArray;
	*/

	int iNumMessages = Asura_StaticMessage_System::GetNumberOfMessagesInBlock(usMessageBlock);
	if(iNumMessages < 2) return;

	//does any sorting need to be done?
	bool bNeedSort = false;
	for(int i=0;i<iNumMessages-1;i++)
	{
		const Asura_StaticMessage* pxMessage1 = Asura_StaticMessage_System::GetMessagePointer(usMessageBlock, i);
		const Asura_StaticMessage* pxMessage2 = Asura_StaticMessage_System::GetMessagePointer(usMessageBlock, i + 1);
		if(pxMessage1->m_fDelay > pxMessage2->m_fDelay)
		{
			bNeedSort = true;
			break;
		}
	}
	if(!bNeedSort) return;
	
	//get all the delays for sorting
	Tools_RowSortInfo* pxSortData = new Tools_RowSortInfo[iNumMessages];
	for(int i=0; i<iNumMessages; i++)
	{
		pxSortData[i].iIndex = i;
		pxSortData[i].fDelay = Asura_StaticMessage_System::GetMessagePointer(usMessageBlock, i)->m_fDelay;
	}

	Asura_SortAndSearch::Quicksort(pxSortData, iNumMessages, SortStaticMessages);

	int iMessageOverlayToOpenIndex = -1;

	//copy the messages to a temporary array
	Asura_StaticMessage* pxMessages = new Asura_StaticMessage[iNumMessages];
	for(int i=0; i<iNumMessages; i++)
	{
		Asura_StaticMessage* pxMessage = Asura_StaticMessage_System::GetMessagePointer(usMessageBlock, i);

		pxMessages[i] = *pxMessage;

		if(pxMessage == s_pxMessageOverlayToOpen)
		{
			iMessageOverlayToOpenIndex = i;
		}
	}
	//and copy back in the new order
	for(int i=0; i<iNumMessages; i++)
	{
		int iSourceIndex = pxSortData[i].iIndex;

		*Asura_StaticMessage_System::GetMessagePointer(usMessageBlock, i) = pxMessages[iSourceIndex];

		if(iSourceIndex == iMessageOverlayToOpenIndex)
		{
			s_pxMessageOverlayToOpen = Asura_StaticMessage_System::GetMessagePointer(usMessageBlock, i);
		}
	}

	delete [] pxSortData;
	delete [] pxMessages;
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

void Tools_MessageEditor::CreateOutgoingMessagesTabPage(Asura_Vector_2 xPosition)
{
	SelectTab(TAB_OUTGOING_MESSAGES);

	// Create the table

	if(s_paxMessageTables[TAB_OUTGOING_MESSAGES] == NULL)
	{
		s_paxMessageTables[TAB_OUTGOING_MESSAGES] = new Asura_MessageTable_Outgoing_Dialog(xPosition, 90.0f, s_pxTabDialog, s_bInMessageEditingMode);
	}

	s_pxTabDialog->AddDialog(s_paxMessageTables[TAB_OUTGOING_MESSAGES]);
	xPosition.y += s_paxMessageTables[TAB_OUTGOING_MESSAGES]->GetHeight();

	// Create resizer control

	Tools_MessageEditor_Resize_Dialog* pxResizeControl = new Tools_MessageEditor_Resize_Dialog(s_pxTabDialog, &s_xDialogSize);
	s_pxOverlay->AddDialog(pxResizeControl);

	// Create the buttons below the table control

	// First row of buttons ...

	float fButtonXOffset = 1.0f;
	float fButtonYOffset = 1.0f;

	Asura_StringDisplay_Dialog* pxMessageLabel = new Asura_StringDisplay_Dialog("Message:", xPosition);
	s_pxTabDialog->AddDialog(pxMessageLabel);
	xPosition.y += Asura_Dialog::DefaultSpacing();
	pxMessageLabel->SetRelativePosition(s_paxMessageTables[TAB_OUTGOING_MESSAGES], Asura_Vector_2(fButtonXOffset, fButtonYOffset), true, false);
	fButtonXOffset += 12.0f;

	s_pxAddMessageButton = new Asura_Function_Dialog("Add New (m)", xPosition, Tools_MessageEditor::AddMessageCallback);
	s_pxTabDialog->AddDialog(s_pxAddMessageButton);
	xPosition.y += Asura_Dialog::DefaultSpacing();
	s_pxAddMessageButton->SetRelativePosition(s_paxMessageTables[TAB_OUTGOING_MESSAGES], Asura_Vector_2(fButtonXOffset, fButtonYOffset), true, false);
	fButtonXOffset += 12.0f;

	s_pxRetargetMessageButton = new Asura_Function_Dialog("Retarget (r)", xPosition, Tools_MessageEditor::ChangeMessageTarget);
	s_pxTabDialog->AddDialog(s_pxRetargetMessageButton);
	xPosition.y += Asura_Dialog::DefaultSpacing();
	s_pxRetargetMessageButton->SetRelativePosition(s_paxMessageTables[TAB_OUTGOING_MESSAGES], Asura_Vector_2(fButtonXOffset, fButtonYOffset), true, false);
	fButtonXOffset += 12.0f;

	s_pxGotoTargetButton = new Asura_Function_Dialog("Goto Target (g)", xPosition, Tools_MessageEditor::GotoTarget);
	s_pxTabDialog->AddDialog(s_pxGotoTargetButton);
	xPosition.y += Asura_Dialog::DefaultSpacing();
	s_pxGotoTargetButton->SetRelativePosition(s_paxMessageTables[TAB_OUTGOING_MESSAGES], Asura_Vector_2(fButtonXOffset, fButtonYOffset), true, false);
	fButtonXOffset += 12.0f;

	s_pxSelectTargetButton = new Asura_Function_Dialog("Select Target (>)", xPosition, Tools_MessageEditor::SelectTarget);
	s_pxTabDialog->AddDialog(s_pxSelectTargetButton);
	xPosition.y += Asura_Dialog::DefaultSpacing();
	s_pxSelectTargetButton->SetRelativePosition(s_paxMessageTables[TAB_OUTGOING_MESSAGES], Asura_Vector_2(fButtonXOffset, fButtonYOffset), true, false);
	fButtonXOffset += 12.0f;

	fButtonYOffset += 1.2f;

	// Second row of buttons ...

	fButtonXOffset = 1.0f;

	Asura_StringDisplay_Dialog* pxBlockLabel = new Asura_StringDisplay_Dialog("Output:", xPosition);
	s_pxTabDialog->AddDialog(pxBlockLabel);
	xPosition.y += Asura_Dialog::DefaultSpacing();
	pxBlockLabel->SetRelativePosition(s_paxMessageTables[TAB_OUTGOING_MESSAGES], Asura_Vector_2(fButtonXOffset, fButtonYOffset), true, false);
	fButtonXOffset += 12.0f;

	s_pxCreateBlockButton = new Asura_Function_Dialog("Create New", xPosition, Tools_MessageEditor::CreateOutputBlockCallback);
	s_pxTabDialog->AddDialog(s_pxCreateBlockButton);
	xPosition.y += Asura_Dialog::DefaultSpacing();
	s_pxCreateBlockButton->SetRelativePosition(s_paxMessageTables[TAB_OUTGOING_MESSAGES], Asura_Vector_2(fButtonXOffset, fButtonYOffset), true, false);
	fButtonXOffset += 12.0f;

	s_pxRenameButton = new Asura_Function_Dialog("Rename", xPosition, Tools_MessageEditor::RenameCallback);
	s_pxTabDialog->AddDialog(s_pxRenameButton);
	xPosition.y += Asura_Dialog::DefaultSpacing();
	s_pxRenameButton->SetRelativePosition(s_paxMessageTables[TAB_OUTGOING_MESSAGES], Asura_Vector_2(fButtonXOffset, fButtonYOffset), true, false);
	fButtonXOffset += 12.0f;

	fButtonYOffset += 1.2f;

	// Third row of buttons ...

	fButtonXOffset = 1.0f;

	Asura_StringDisplay_Dialog* pxMiscLabel = new Asura_StringDisplay_Dialog("Misc:", xPosition);
	s_pxTabDialog->AddDialog(pxMiscLabel);
	xPosition.y += Asura_Dialog::DefaultSpacing();
	pxMiscLabel->SetRelativePosition(s_paxMessageTables[TAB_OUTGOING_MESSAGES], Asura_Vector_2(fButtonXOffset, fButtonYOffset), true, false);
	fButtonXOffset += 12.0f;

	s_pxDeleteMessageButton = new Asura_Function_Dialog("Delete", xPosition, Tools_MessageEditor::DeleteMessage);
	s_pxTabDialog->AddDialog(s_pxDeleteMessageButton);
	xPosition.y += Asura_Dialog::DefaultSpacing();
	s_pxDeleteMessageButton->SetRelativePosition(s_paxMessageTables[TAB_OUTGOING_MESSAGES], Asura_Vector_2(fButtonXOffset, fButtonYOffset), true, false);
	fButtonXOffset += 12.0f;

	s_pxCopyMessageButton = new Asura_Function_Dialog("Copy", xPosition, Tools_MessageEditor::CopyToClipboard);
	s_pxTabDialog->AddDialog(s_pxCopyMessageButton);
	xPosition.y += Asura_Dialog::DefaultSpacing();
	s_pxCopyMessageButton->SetRelativePosition(s_paxMessageTables[TAB_OUTGOING_MESSAGES], Asura_Vector_2(fButtonXOffset, fButtonYOffset), true, false);
	fButtonXOffset += 12.0f;

	s_pxPasteMessageButton = new Asura_Function_Dialog("Paste", xPosition, Tools_MessageEditor::PasteFromClipboard);
	s_pxTabDialog->AddDialog(s_pxPasteMessageButton);
	xPosition.y += Asura_Dialog::DefaultSpacing();
	s_pxPasteMessageButton->SetRelativePosition(s_paxMessageTables[TAB_OUTGOING_MESSAGES], Asura_Vector_2(fButtonXOffset, fButtonYOffset), true, false);
	fButtonXOffset += 12.0f;

	s_pxCollapseAllButton = new Asura_Function_Dialog("Collapse All", xPosition, Tools_MessageEditor::CollapseAllRows);
	s_pxTabDialog->AddDialog(s_pxCollapseAllButton);
	xPosition.y += Asura_Dialog::DefaultSpacing();
	s_pxCollapseAllButton->SetRelativePosition(s_paxMessageTables[TAB_OUTGOING_MESSAGES], Asura_Vector_2(fButtonXOffset, fButtonYOffset), true, false);
	fButtonXOffset += 12.0f;

	s_pxExpandAllButton = new Asura_Function_Dialog("Expand All", xPosition, Tools_MessageEditor::ExpandAllRows);
	s_pxTabDialog->AddDialog(s_pxExpandAllButton);
	xPosition.y += Asura_Dialog::DefaultSpacing();
	s_pxExpandAllButton->SetRelativePosition(s_paxMessageTables[TAB_OUTGOING_MESSAGES], Asura_Vector_2(fButtonXOffset, fButtonYOffset), true, false);
	fButtonXOffset += 12.0f;

	Asura_Dialog* pxDialog = new Asura_Function_Dialog("Delete Orphans", xPosition, Tools_Object_System::DeleteOrphanedMessages);
	pxDialog->SetHelpText("This attempts to delete any message blocks that no entity claims ownership of.");
	s_pxTabDialog->AddDialog(pxDialog);
	pxDialog->SetRelativePosition(s_paxMessageTables[TAB_OUTGOING_MESSAGES], Asura_Vector_2(fButtonXOffset, fButtonYOffset), true, false);
	fButtonXOffset += 12.0f;

}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

void Tools_MessageEditor::CreateIncomingMessagesTabPage(Asura_Vector_2 xPosition)
{
	SelectTab(TAB_INCOMING_MESSAGES);

	// Create the table

	if(s_paxMessageTables[TAB_INCOMING_MESSAGES] == NULL)
	{
		s_paxMessageTables[TAB_INCOMING_MESSAGES] = new Asura_MessageTable_Incoming_Dialog(xPosition, 90.0f, s_pxTabDialog, s_bInMessageEditingMode);
	}

	s_pxTabDialog->AddDialog(s_paxMessageTables[TAB_INCOMING_MESSAGES]);
	xPosition.y += s_paxMessageTables[TAB_INCOMING_MESSAGES]->GetHeight();

	// Create resizer control

	Tools_MessageEditor_Resize_Dialog* pxResizeControl = new Tools_MessageEditor_Resize_Dialog(s_pxTabDialog, &s_xDialogSize);
	s_pxOverlay->AddDialog(pxResizeControl);

	// Create the buttons below the table control

	// First row of buttons ...

	float fButtonXOffset = 1.0f;
	float fButtonYOffset = 1.0f;

	Asura_StringDisplay_Dialog* pxMessageLabel = new Asura_StringDisplay_Dialog("Message:", xPosition);
	s_pxTabDialog->AddDialog(pxMessageLabel);
	xPosition.y += Asura_Dialog::DefaultSpacing();
	pxMessageLabel->SetRelativePosition(s_paxMessageTables[TAB_INCOMING_MESSAGES], Asura_Vector_2(fButtonXOffset, fButtonYOffset), true, false);
	fButtonXOffset += 12.0f;

	s_pxSelectTargetButton = new Asura_Function_Dialog("Edit Message", xPosition, Tools_MessageEditor::EditMessageCallback);
	s_pxTabDialog->AddDialog(s_pxSelectTargetButton);
	xPosition.y += Asura_Dialog::DefaultSpacing();
	s_pxSelectTargetButton->SetRelativePosition(s_paxMessageTables[TAB_INCOMING_MESSAGES], Asura_Vector_2(fButtonXOffset, fButtonYOffset), true, false);
	fButtonXOffset += 12.0f;

	s_pxGotoTargetButton = new Asura_Function_Dialog("Goto Sender (g)", xPosition, Tools_MessageEditor::GotoSource);
	s_pxTabDialog->AddDialog(s_pxGotoTargetButton);
	xPosition.y += Asura_Dialog::DefaultSpacing();
	s_pxGotoTargetButton->SetRelativePosition(s_paxMessageTables[TAB_INCOMING_MESSAGES], Asura_Vector_2(fButtonXOffset, fButtonYOffset), true, false);
	fButtonXOffset += 12.0f;

	s_pxSelectTargetButton = new Asura_Function_Dialog("Select Sender (>)", xPosition, Tools_MessageEditor::SelectSource);
	s_pxTabDialog->AddDialog(s_pxSelectTargetButton);
	xPosition.y += Asura_Dialog::DefaultSpacing();
	s_pxSelectTargetButton->SetRelativePosition(s_paxMessageTables[TAB_INCOMING_MESSAGES], Asura_Vector_2(fButtonXOffset, fButtonYOffset), true, false);
	fButtonXOffset += 12.0f;

	fButtonYOffset += 1.2f;

	Update();
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

void Tools_MessageEditor::SetHideButtons(bool bFlag)
{
	if(s_pxOverlay)
	{
		s_pxAddMessageButton->SetEnabled(!bFlag);
		s_pxRetargetMessageButton->SetEnabled(!bFlag);
		s_pxGotoTargetButton->SetEnabled(!bFlag);
		s_pxSelectTargetButton->SetEnabled(!bFlag);
		s_pxCreateBlockButton->SetEnabled(!bFlag);
		s_pxRenameButton->SetEnabled(!bFlag);
		s_pxDeleteMessageButton->SetEnabled(!bFlag);
		s_pxCopyMessageButton->SetEnabled(!bFlag);
		s_pxPasteMessageButton->SetEnabled(!bFlag);
	}
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

void Tools_MessageEditor::StartMessageEditingMode(u_short* pusMessageBlockToSelect /* = NULL */)
{
	if(Tools_Message_Editing::IsInMessageEditingMode())
	{
		return;
	}

	if(Tools_ChooseMessageTargetObject::IsInRetargetMode())
	{
		return;
	}

	if(!s_bInMessageEditingMode)
	{
		//Make sure the message editor overlay exists , so that the correct row can be selected. RAR 19/02/2009
		if(!s_bIsOverlayVisible) CreateOverlay();
		
		switch(Tools_Object_System::SelectedObjectList.size())
		{
			case 0:
			{
				Tools_Timed_Message_System::AddMessage (0xffff00, "Please select an object before entering messaging editing mode.");
				break;
			}
			case 1:
			{
				if(!Tools_Object_System::SelectedObject->SupportsMessageEditor())
				{
					break;
				}

				s_bInMessageEditingMode = true;
				s_pusMessageBlockToSelect = pusMessageBlockToSelect;

				SetHideButtons(true);

				//Asura_Overlay_System::SetHideOverlays(true);

				if(s_pxOverlay == NULL)
				{
					Tools_MessageEditor::CreateOverlay();
				}
				else
				{
					// Select the appropriate row

					if(pusMessageBlockToSelect)
					{
						Asura_List<Asura_MessageTableRow>& xRows = s_paxMessageTables[TAB_OUTGOING_MESSAGES]->GetRows();

						Asura_List_It<Asura_MessageTableRow> xIter(xRows);

						for(; !xIter.Done(); xIter.Next())
						{
							Asura_MessageTableRow* pxRow = xIter.GetCurrent();

							if(pxRow->GetBlockId() == pusMessageBlockToSelect)
							{
								pxRow->SetSelected(true);
							}
							else
							{
								pxRow->SetSelected(false);
							}
						}
					}
				}

				SetReferenceObject(Tools_Object_System::SelectedObject);

				s_pxOverlay->SetOverrideOverlaysHiddenFlag(true);

				Tools_ChooseAddMessageTargets* pxChooseObject = new Tools_ChooseAddMessageTargets(s_pxReferenceObject, s_paxMessageTables[TAB_OUTGOING_MESSAGES]);
				Tools_Object_System::ChooseObject("Please choose target entities for this new message.", pxChooseObject);

				break;
			}
			default: // More than one object selected
			{
				if(!Tools_Object_System::SelectedObject->SupportsMessageEditor())
				{
					break;
				}

				// Ask the user what message they want to send

				Asura_List<Asura_Guid> xSelectionList;

				for(CLIF<Tools_Object*> xIter(&Tools_Object_System::SelectedObjectList); !xIter.done(); xIter.next())
				{
					if(xIter() != Tools_Object_System::SelectedObject)
					{
						Asura_Guid* pxGuid = new Asura_Guid;
						*pxGuid = xIter()->GetGuid();
						xSelectionList.AddToList(pxGuid);
					}
				}

				Asura_StaticMessage xMessage;

				bool bEditedMessageID = Tools_Message_Editing::s_pxThis->EditMessageID(&xMessage, "Select message to send", xSelectionList);

				while (xSelectionList.GetNumEntries())
				{
					Asura_Guid* pxGuid = xSelectionList.PopHeadObject();
					delete pxGuid;
				}
				if (!bEditedMessageID)
				{
					break;
				}

				SetReferenceObject(Tools_Object_System::SelectedObject);

				for(CLIF<Tools_Object*> xIter(&Tools_Object_System::SelectedObjectList); !xIter.done(); xIter.next())
				{
					if(xIter() != Tools_Object_System::SelectedObject)
					{
						if(xIter()->SupportsMessageEditor())
						{
							Tools_MessageEditor::AddMessage(xMessage.m_usMsgID, xIter()->GetGuid());
						}
					}
				}

				StopMessageEditingMode();

				break;
			}
		}
	}
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

void Tools_MessageEditor::StopMessageEditingMode()
{
	if(s_bInMessageEditingMode)
	{
		s_bInMessageEditingMode = false;

		if(s_pxOverlay)
		{
			s_pxOverlay->SetOverrideOverlaysHiddenFlag(false);
		}

		Asura_Overlay* pxEntityManagerOverlay = Asura_Overlay_System::GetOverlay(TOOLS_OVERLAY_SECONDARYENTFILE);
		if(pxEntityManagerOverlay)
		{
			pxEntityManagerOverlay->SetOverrideOverlaysHiddenFlag(false);
		}

		Tools_MessageEditor::UpdateObjectOverlay();

		SetHideButtons(false);

		s_bForceRecreateTable = true;
	}

	s_pusMessageBlockToSelect = NULL;
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

void Tools_MessageEditor::AddMessageCallback()
{
	Tools_MessageEditor::StartMessageEditingMode();
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

Asura_StaticMessage* Tools_MessageEditor::AddMessage(u_short usMessageId, Asura_Guid xTargetGuid)
{
	u_short* pusBlockId = GetBlockIdFromRowSelection(true);

	if(!pusBlockId)
	{
		// Create a new block
		pusBlockId = CreateOutputBlock("Default");
	}

	if(pusBlockId)
	{
		Asura_StaticMessage message;
		message.Initialise();

		message.m_fDelay = 0.0f;
		message.m_fProbability = 1.0f;
		message.m_usMsgID = usMessageId;
		message.m_uSendTo = xTargetGuid;

		Tools_Message_Editing::s_pxThis->InitialiseMessageData(&message, true);

		Asura_StaticMessageSetIndex eSet = GetMessageSetFromBlockId(pusBlockId);

		Asura_StaticMessage_System::AddMessage(eSet, message, *pusBlockId);

		SetModified(pusBlockId);

		// Get the message pointer to return
		int iNumMessages = Asura_StaticMessage_System::GetNumberOfMessagesInBlock(*pusBlockId);
		Asura_StaticMessage* pxNewMessage = Asura_StaticMessage_System::GetMessagePointer(*pusBlockId, iNumMessages - 1);

		pxNewMessage->m_uToolsFlags |= uTOOLS_STATICMESSAGE_FLAG_MESSAGEEDITOR_SELECTED;
		s_bForceRecreateTable = true;

		s_pxMessageOverlayToOpen = pxNewMessage;
		Tools_MessageEditor::CreateMessageAttributesOverlay(pxNewMessage);

		return pxNewMessage;
	}

	return NULL;
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

void Tools_MessageEditor::CreateOutputBlockCallback()
{
	CreateOutputBlock();
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

void Tools_MessageEditor::RenameCallback()
{
	// Make sure that only one row is selected

	u_int uSelectedCount = 0;

	Asura_List<Asura_MessageTableRow>& xRows = s_paxMessageTables[TAB_OUTGOING_MESSAGES]->GetRows();

	Asura_List_It<Asura_MessageTableRow> xIter(xRows);

	Asura_MessageTableRow* pxSelectedRow;

	for(; !xIter.Done(); xIter.Next())
	{
		Asura_MessageTableRow* pxRow = xIter.GetCurrent();

		if(pxRow->IsSelected())
		{
			pxSelectedRow = pxRow;
			uSelectedCount++;
		}
	}

	if(uSelectedCount != 1)
	{
		Tools_Timed_Message_System::AddMessage(0xffff00, "Only one row must be selected to rename!");
		return;
	}

	// Make sure that this row is renameable

	bool bRenamable = false;

	//if(pxSelectedRow->IsRenameable())
	{
		switch(pxSelectedRow->GetType())
		{
			case Asura_MessageTableRow::TYPE_OUTGOING_EVENT_HEADER:
			{
				// Is this a custom event?

				int iCustomEventIndex = -1;

				int iNumCustomEvents = s_pxReferenceEntity->GetNumCustomEvents();

				for(int n = 0; n < iNumCustomEvents; n++)
				{
					u_short* pusBlockId = s_pxReferenceEntity->GetCustomEvent(n);

					if(pusBlockId == pxSelectedRow->GetBlockId())
					{
						iCustomEventIndex = n;
						break;
					}
				}

				if(iCustomEventIndex != -1 && s_pxReferenceToolsObject)
				{
					if(s_pxReferenceToolsObject->RenameCustomEvent(iCustomEventIndex))
					{
						bRenamable = true;
					}
				}

				break;

				/*
				Asura_MessageTableRow_OutgoingEventHeader* pxOutgoingMessageHeaderRow = static_cast<Asura_MessageTableRow_OutgoingEventHeader*> (pxSelectedRow);

				GetString_Overlay xOverlay("Enter new name", pxOutgoingMessageHeaderRow->GetText().GetString());

				if(!xOverlay.DoModal()) return;

				pxOutgoingMessageHeaderRow->SetText(xOverlay.GetAsuraString());
				*/
			}
			case Asura_MessageTableRow::TYPE_OUTGOING_MESSAGE:
			case Asura_MessageTableRow::TYPE_INCOMING_MESSAGE:
			{
				break;
			}
		}
	}

	if(!bRenamable)
	{
		Tools_Timed_Message_System::AddMessage(0xffff00, "This row is not renameable");
	}
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

u_short* Tools_MessageEditor::GetCachedBlockIdFromRowSelection()
{
	return s_pusCachedBlockIdFromRowSelection;
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

u_short* Tools_MessageEditor::GetBlockIdFromRowSelection(bool bCreateBlockIfNeeded)
{
	// See if there's a row selected, if so, get this row's block id to put this new message in

	u_short* pusBlockId = NULL;

	bool bFound = false;

	if(s_paxMessageTables[TAB_OUTGOING_MESSAGES] == NULL)
	{
		return GetCachedBlockIdFromRowSelection();
	}

	Asura_List<Asura_MessageTableRow>& xRows = s_paxMessageTables[TAB_OUTGOING_MESSAGES]->GetRows();

	Asura_List_It<Asura_MessageTableRow> xIter(xRows);

	for(; !xIter.Done(); xIter.Next())
	{
		Asura_MessageTableRow* pxRow = xIter.GetCurrent();

		if(pxRow->IsSelected())
		{
			switch(pxRow->GetType())
			{
				case Asura_MessageTableRow::TYPE_OUTGOING_MESSAGE:
				{
					Asura_MessageTableRow_Outgoing* pxOutgoingMessageRow = static_cast<Asura_MessageTableRow_Outgoing*> (pxRow);

					pusBlockId = pxOutgoingMessageRow->GetBlockId();
					bFound = true;
					break;
				}

				case Asura_MessageTableRow::TYPE_OUTGOING_EVENT_HEADER:
				{
					Asura_MessageTableRow_OutgoingEventHeader* pxOutgoingMessageHeaderRow = static_cast<Asura_MessageTableRow_OutgoingEventHeader*> (pxRow);

					pusBlockId = pxOutgoingMessageHeaderRow->GetBlockId();

					bFound = true;

					break;
				}
			}

			break;
		}
	}

	if(!bFound && bCreateBlockIfNeeded)
	{
		// No relevant rows selected, so add it to the trigger event if compatible, if not use the activate event

		// But first check to see if this entity is a trigger - if so, then need to hardwire it to the trigger event (as trigger events
		// are handled differently for some reason).

		Asura_ServerEntity_Trigger* pxTrigger = static_cast<Asura_ServerEntity_Trigger*> (Asura_ServerEntity_Trigger::FindEntity(s_pxReferenceObject->GetGuid()));

		if(pxTrigger)
		{
			if(s_pxReferenceObject->HandlesThisMessage(ASURA_ENTITY_EVENT_ONBEINGTRIGGERED))
			{
				pusBlockId = pxTrigger->GetMessageBlockPointer(ASURA_TRIGGER_ENTITY_ONBEINGTRIGGERED);
				bFound = true;
			}
			else if(s_pxReferenceObject->HandlesThisMessage(ASURA_ENTITY_EVENT_ONACTIVATION))
			{
				pusBlockId = pxTrigger->GetMessageBlockPointer(ASURA_TRIGGER_ENTITY_ONACTIVATION);
				bFound = true;
			}
		}
		else
		{
			bool bFound2 = false;

			for(int n = 0; n < 3; n++)
			{
				const Asura_EventAndMessage* paxEvents = s_pxReferenceEntity->GetEvents();

				int iEventId;

				if(n == 0)
				{
					iEventId = s_pxReferenceEntity->GetDefaultMessageEvent();

					if(iEventId == -1)
					{
						continue;
					}
				}

				if(n == 1)
				{
					iEventId = ASURA_ENTITY_EVENT_ONBEINGTRIGGERED;
				}

				if(n == 2)
				{
					iEventId = ASURA_ENTITY_EVENT_ONACTIVATION;
				}

				int iNumExternalEvents = s_pxReferenceEntity->GetNumEvents();

				for(int i = 0; i < iNumExternalEvents; i++)
				{
					if(paxEvents[i].m_usEventID == iEventId)
					{
						pusBlockId = (unsigned short*) (&paxEvents[i].m_usMessageBlockID);
						bFound = true;
						bFound2 = true;
						break;
					}
				}

				if(bFound2)
				{
					break;
				}
				else if(s_pxReferenceToolsObject)
				{
					if(s_pxReferenceToolsObject->HandlesThisEvent(iEventId))
					{
						s_pxReferenceToolsObject->AddEventMessageBlock(iEventId);
						n--;
						continue;
					}
				}
			}

			if(!bFound)
			{
				// Object doesn't support 'on being triggered' or 'on activate', so get the first event type it does support

				const Asura_EventAndMessage* paxEvents = s_pxReferenceEntity->GetEvents();

				int iNumExternalEvents = s_pxReferenceEntity->GetNumEvents();

				if(iNumExternalEvents > 0)
				{
					pusBlockId = (unsigned short*) (&paxEvents[0].m_usMessageBlockID);
					bFound = true;
					bFound2 = true;
				}
				else if(s_pxReferenceToolsObject)
				{
					const EventIDAndDescription* pxEventList = Tools_Message_Editing::GetEventList();

					if(pxEventList)
					{
						for(const EventIDAndDescription* pxEvent = pxEventList; pxEvent->m_pszDescription; pxEvent++)
						{
							if(s_pxReferenceToolsObject->HandlesThisEvent(pxEvent->m_iEventID))
							{
								// Have we a message block for this event?

								for(u_int n = 0; n < 2; n++)
								{
									bool bEventExists = false;

									Asura_Entity_Server* pxObject = static_cast<Asura_Entity_Server*> (Asura_Entity_Server::FindEntity(s_pxReferenceObject->GetGuid()));

									const Asura_EventAndMessage* pxExistingEvents = pxObject->GetEvents();

									for(int i = 0; i < pxObject->GetNumEvents(); i++)
									{
										if(pxExistingEvents[i].m_usEventID == pxEvent->m_iEventID)
										{
											pusBlockId = (unsigned short*) (&pxExistingEvents[i].m_usMessageBlockID);
											bFound = true;
											bFound2 = true;
											break;
										}
									}

									if(bFound2)
									{
										break;
									}

									if(!bEventExists)
									{
										s_pxReferenceToolsObject->AddEventMessageBlock(pxEvent->m_iEventID);
									}
								}
							}

							if(bFound)
							{
								break;
							}
						}
					}
				}
			}

			if(!bFound)
			{
				// Still not found an event block, so check for custom events

				int iNumCustomEvents = s_pxReferenceEntity->GetNumCustomEvents();

				if(iNumCustomEvents > 0)
				{
					pusBlockId = s_pxReferenceEntity->GetCustomEvent(0);
					bFound = true;
				}
			}
		}
	}

	return pusBlockId;
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

void Tools_MessageEditor::CopyToClipboard()
{
	SendToClipboard(false);
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

void Tools_MessageEditor::CutToClipboard()
{
	SendToClipboard(true);
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

void Tools_MessageEditor::SendToClipboard(bool bCut)
{
	xClipboard.Clear();

	xClipboard.SetMode(bCut ? Tools_MessageEditor_Clipboard::MESSAGEEDITOR_CLIPBOARDMODE_CUT : Tools_MessageEditor_Clipboard::MESSAGEEDITOR_CLIPBOARDMODE_COPY);

	switch(s_pxTabDialog->GetSelected())
	{
		case TAB_OUTGOING_MESSAGES:		xClipboard.SetType(Tools_MessageEditor_Clipboard::MESSAGEEDITOR_CLIPBOARDTYPE_OUTGOING);	break;
		case TAB_INCOMING_MESSAGES:		xClipboard.SetType(Tools_MessageEditor_Clipboard::MESSAGEEDITOR_CLIPBOARDTYPE_INCOMING);	break;
	}

	Asura_List<Asura_MessageTableRow>& xRows = s_paxMessageTables[s_pxTabDialog->GetSelected()]->GetRows();

	Asura_List_It<Asura_MessageTableRow> xIter(xRows);

	for(; !xIter.Done(); xIter.Next())
	{
		Asura_MessageTableRow* pxRow = xIter.GetCurrent();

		if(pxRow->IsSelected())
		{
			if(pxRow->GetType() == Asura_MessageTableRow::TYPE_OUTGOING_MESSAGE)
			{
				Asura_MessageTableRow_Outgoing* pxOutgoingMessageRow = static_cast<Asura_MessageTableRow_Outgoing*> (pxRow);

				xClipboard.Add(pxOutgoingMessageRow->GetOutgoingMessagePointer(), NULL);
			}

			if(pxRow->GetType() == Asura_MessageTableRow::TYPE_INCOMING_MESSAGE)
			{
				Asura_MessageTableRow_Incoming* pxIncomingMessageRow = static_cast<Asura_MessageTableRow_Incoming*> (pxRow);

				Tools_MessageEditor_ReceivedMessageData* pxReceivedMessageData;

				pxReceivedMessageData = pxIncomingMessageRow->GetReceivedMessageData();

				xClipboard.Add(pxReceivedMessageData->m_pxStaticMessage, pxReceivedMessageData->m_uMessageBlockId);
			}
		}
	}

	if(bCut)
	{
		// We're cutting, so do a delete now that we've copied the data into the clipboard

		DeleteMessage();
	}

	Tools_Timed_Message_System::AddMessage(0xffffff, "%u message(s) copied", xClipboard.Count());
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

void Tools_MessageEditor::PasteFromClipboard()
{
	if(!s_pxReferenceEntity) return;

	Tools_MessageEditor_ClipboardItem* pxMessages = xClipboard.GetArray();
	u_int uNumMessages = xClipboard.Count();

	for(u_int n = 0; n < uNumMessages; n++)
	{
		Asura_StaticMessage xMessage;

		xMessage.Initialise();

		xMessage.Copy(&pxMessages[n].xMessage);

		if(s_pxTabDialog->GetSelected() == TAB_OUTGOING_MESSAGES)
		{
			if(xClipboard.GetType() != Tools_MessageEditor_Clipboard::MESSAGEEDITOR_CLIPBOARDTYPE_OUTGOING)
			{
				Tools_Timed_Message_System::AddMessage (0xff0000, "Cannot paste from the incoming message tab to the outgoing message tab");
			}
			else
			{
				u_short* pusBlockId = GetBlockIdFromRowSelection(true);
				if(!pusBlockId) return;
				Asura_StaticMessageSetIndex eSet = GetMessageSetFromBlockId(pusBlockId);

				Asura_StaticMessage_System::AddMessage(eSet, xMessage, *pusBlockId);
			}
		}

		if(s_pxTabDialog->GetSelected() == TAB_INCOMING_MESSAGES)
		{
			if(xClipboard.GetType() != Tools_MessageEditor_Clipboard::MESSAGEEDITOR_CLIPBOARDTYPE_INCOMING)
			{
				Tools_Timed_Message_System::AddMessage (0xff0000, "Cannot paste from the outgoing message tab to the incoming message tab");
			}
			else
			{
				Asura_StaticMessageSetIndex eSet = GetMessageSetFromBlockId(&pxMessages[n].usSourceBlockId);

				xMessage.m_uSendTo = s_pxReferenceEntity->GetGuid();

				Asura_StaticMessage_System::AddMessage(eSet, xMessage, pxMessages[n].usSourceBlockId);
			}
		}

		Tools_SaveAsura::SetEntityModified(true);
	}

	s_bForceRecreateTable = true;
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

Asura_StaticMessage* Tools_MessageEditor::GetSelectedStaticMessage()
{
	if(s_pxOverlay)
	{
		if(s_pxTabDialog->GetSelected() == TAB_OUTGOING_MESSAGES)
		{
			if(s_paxMessageTables[TAB_OUTGOING_MESSAGES]->GetNumSelectedRows() == 1)
			{
				Asura_List<Asura_MessageTableRow>& xRows = s_paxMessageTables[TAB_OUTGOING_MESSAGES]->GetRows();
				Asura_List_It<Asura_MessageTableRow> xIter(xRows);

				for(; !xIter.Done(); xIter.Next())
				{
					Asura_MessageTableRow* pxRow = xIter.GetCurrent();

					if(pxRow->IsSelected())
					{
						if(pxRow->GetType() == Asura_MessageTableRow::TYPE_OUTGOING_MESSAGE)
						{
							Asura_MessageTableRow_Outgoing* pxOutgoingMessageRow = static_cast<Asura_MessageTableRow_Outgoing*> (pxRow);

							return pxOutgoingMessageRow->GetOutgoingMessagePointer();
						}
					}
				}
			}
		}
	}

	return NULL;
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

void Tools_MessageEditor::CollapseAllRows()
{
	s_paxMessageTables[s_pxTabDialog->GetSelected()]->CollapseAllRows();
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

void Tools_MessageEditor::ExpandAllRows()
{
	s_paxMessageTables[s_pxTabDialog->GetSelected()]->ExpandAllRows();
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

bool Tools_MessageEditor::IsCustomEventRow(Asura_MessageTableRow* pxRow, int& iCustomEventIndex, bool bCheckEventRowsOnly /* = true */)
{
	if(!bCheckEventRowsOnly || (pxRow->GetType() == Asura_MessageTableRow::TYPE_OUTGOING_EVENT_HEADER))
	{
		// Is this a custom event?

		iCustomEventIndex = -1;

		int iNumCustomEvents = s_pxReferenceEntity->GetNumCustomEvents();

		for(int n = 0; n < iNumCustomEvents; n++)
		{
			u_short* pusBlockId = s_pxReferenceEntity->GetCustomEvent(n);

			if(pusBlockId == pxRow->GetBlockId())
			{
				iCustomEventIndex = n;
				break;
			}
		}

		if(iCustomEventIndex != -1)
		{
			return true;
		}
	}

	return false;
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

void Tools_MessageEditor::DeleteMessage()
{
	if(s_pxReferenceObject == NULL || s_pxReferenceEntity == NULL)
	{
		return;
	}

	bool bRetValue = false;

	// Scan through the list of rows looking for selected items to delete

	Asura_List<Asura_MessageTableRow>& xRows = s_paxMessageTables[TAB_OUTGOING_MESSAGES]->GetRows();

	Asura_List_It<Asura_MessageTableRow> xIter(xRows);

	List<ToDeleteStruct*> xToDeleteList;

	int iCustomEventIndex;

	// Initialise the patchup data

	int iPatchupIndexTableSize = s_pxReferenceEntity->GetNumCustomEvents();
	Asura_MessageTableRow** paxPatchupIndexTable = new Asura_MessageTableRow*[iPatchupIndexTableSize];

	for(; !xIter.Done(); xIter.Next())
	{
		if(IsCustomEventRow(xIter.GetCurrent(), iCustomEventIndex))
		{
			paxPatchupIndexTable[iCustomEventIndex] = xIter.GetCurrent();
		}
	}

	xIter.Restart();

	for(; !xIter.Done(); xIter.Next())
	{
		if(xIter.GetCurrent()->IsSelected())
		{
			Asura_MessageTableRow* pxSelectedRow = xIter.GetCurrent();

			switch(pxSelectedRow->GetType())
			{
				case Asura_MessageTableRow::TYPE_OUTGOING_EVENT_HEADER:
				{
					if(IsCustomEventRow(pxSelectedRow, iCustomEventIndex) && s_pxReferenceToolsObject)
					{
						if(s_pxReferenceToolsObject->DeleteCustomEvent(iCustomEventIndex))
						{
							// Patch up block id pointers, as some DeleteCustomEvents recreate their arrays.  Note that these pointers will get recreated anyway the next
							// frame, as the table information gets rebuilt when s_bForceRecreateTable is set (see below).  The reason I'm patching up the pointers here
							// is if the user is trying to delete more than one row in one go.

							for(int n = iCustomEventIndex; n < (iPatchupIndexTableSize - 1); n++)
							{
								paxPatchupIndexTable[n] = paxPatchupIndexTable[n + 1];
							}

							iPatchupIndexTableSize--;

							for(int n = 0; n < iPatchupIndexTableSize; n++)
							{
								u_short* pusNewBlockIdPtr = s_pxReferenceToolsObject->GetServerEntity()->GetCustomEvent(n);

								Asura_Assert(pusNewBlockIdPtr, "Custom event pointer is null");

								if(pusNewBlockIdPtr)
								{
									paxPatchupIndexTable[n]->SetBlockId(pusNewBlockIdPtr);
								}
							}
						}
						else
						{
							Tools_Timed_Message_System::AddMessage (0xff0000, "This entity type doesn't support deleting custom messaged from the message editor.");
						}

						bRetValue = true;
					}

					break;
				}
				case Asura_MessageTableRow::TYPE_OUTGOING_MESSAGE:
				{
					break;
				}
			}
		}
	}

	delete [] paxPatchupIndexTable;

	if(!bRetValue)
	{
		// Is this a trigger object

		Asura_ServerEntity_Trigger* pxTrigger = static_cast<Asura_ServerEntity_Trigger*> (Asura_ServerEntity_Trigger::FindEntity(s_pxReferenceObject->GetGuid()));

		if(pxTrigger)
		{
			DeleteTriggerObjectMessage();
		}
		else
		{
			// Otherwise presume it's a normal entity object
			DeleteNormalMessage();
		}
	}

	UpdateObjectOverlay();
	s_bForceRecreateTable = true;
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

void Tools_MessageEditor::DeleteNormalMessage()
{
	Asura_List<Asura_MessageTableRow>& xRows = s_paxMessageTables[TAB_OUTGOING_MESSAGES]->GetRows();

	Asura_List_It<Asura_MessageTableRow> xIter(xRows);

	List<ToDeleteStruct*> xToDeleteList;

	u_int uRowIndex = 0;

	for(; !xIter.Done(); xIter.Next(), uRowIndex++)
	{
		if(xIter.GetCurrent()->IsSelected())
		{
			Asura_MessageTableRow* pxSelectedRow = xIter.GetCurrent();

			switch(pxSelectedRow->GetType())
			{
				case Asura_MessageTableRow::TYPE_OUTGOING_MESSAGE:
				{
					Asura_MessageTableRow_Outgoing* pxOutgoingMessageRow = static_cast<Asura_MessageTableRow_Outgoing*> (pxSelectedRow);

					Asura_StaticMessage* pxMessage = pxOutgoingMessageRow->GetOutgoingMessagePointer();

					// Find the message index

					bool bFound = false;
					int iNumMessagesInBlock;

					const Asura_EventAndMessage* paxEvents = s_pxReferenceEntity->GetEvents();

					int iNumExternalEvents = s_pxReferenceEntity->GetNumEvents();

					for(int n = 0; n < iNumExternalEvents; n++)
					{
						const Asura_EventAndMessage* pxEvent = &paxEvents[n];

						iNumMessagesInBlock = Asura_StaticMessage_System::GetNumberOfMessagesInBlock(pxEvent->m_usMessageBlockID);

						if(pxEvent->m_usMessageBlockID == *pxOutgoingMessageRow->GetBlockId())
						{
							for(int i = 0; i < iNumMessagesInBlock; i++)
							{
								Asura_StaticMessage* pxMessage2 = Asura_StaticMessage_System::GetMessagePointer(pxEvent->m_usMessageBlockID, i);

								if(pxMessage == pxMessage2)
								{
									u_short* pusBlockId = pxOutgoingMessageRow->GetBlockId();

									ToDeleteStruct* pxToDelete = new ToDeleteStruct;
									Asura_Mem::Set(pxToDelete, 0xcd, sizeof(ToDeleteStruct));
									pxToDelete->uRowIndex = uRowIndex;
									pxToDelete->pusBlockId = pusBlockId;
									Asura_StaticMessage_System::ConvertIDToIndex(*pusBlockId, &pxToDelete->iBlockIndex, &pxToDelete->eSet);
									pxToDelete->iMessageIndex = i;
									xToDeleteList.add_entry(pxToDelete);

									bFound = true;
									break;
								}
							}
						}

						if(bFound)
						{
							break;
						}
					}

					// Custom messages

					int iNumCustomEvents = s_pxReferenceEntity->GetNumCustomEvents();

					for(int n = 0; n < iNumCustomEvents; n++)
					{
						u_short* pusCustomEvent = s_pxReferenceEntity->GetCustomEvent(n);

						Asura_Assert(pusCustomEvent, "Custom event pointer is null");

						if(!pusCustomEvent)
						{
							continue;
						}

						iNumMessagesInBlock = Asura_StaticMessage_System::GetNumberOfMessagesInBlock(*pusCustomEvent);

						if(*pusCustomEvent == *pxOutgoingMessageRow->GetBlockId())
						{
							for(int i = 0; i < iNumMessagesInBlock; i++)
							{
								Asura_StaticMessage* pxMessage2 = Asura_StaticMessage_System::GetMessagePointer(*pusCustomEvent, i);

								if(pxMessage == pxMessage2)
								{
									u_short* pusBlockId = pxOutgoingMessageRow->GetBlockId();

									ToDeleteStruct* pxToDelete = new ToDeleteStruct;
									Asura_Mem::Set(pxToDelete, 0xcd, sizeof(ToDeleteStruct));
									pxToDelete->uRowIndex = uRowIndex;
									pxToDelete->pusBlockId = pusBlockId;
									Asura_StaticMessage_System::ConvertIDToIndex(*pusBlockId, &pxToDelete->iBlockIndex, &pxToDelete->eSet);
									pxToDelete->iMessageIndex = i;
									xToDeleteList.add_entry(pxToDelete);

									bFound = true;
									break;
								}
							}
						}

						if(bFound)
						{
							break;
						}
					}

					break;
				}
				case Asura_MessageTableRow::TYPE_OUTGOING_EVENT_HEADER:
				{
					Asura_MessageTableRow_OutgoingEventHeader* pxOutgoingEventHeaderRow = static_cast<Asura_MessageTableRow_OutgoingEventHeader*> (pxSelectedRow);
					//Asura_StaticMessage_System::DeleteMessageBlock(*pxOutgoingEventHeaderRow->GetBlockId());
					//s_pxReferenceToolsObject->RemoveEventMessageBlock(*pxOutgoingEventHeaderRow->GetBlockId());

					if(pxOutgoingEventHeaderRow->GetEventId() != -1)
					{
						ToDeleteStruct* pxToDelete = new ToDeleteStruct;
						Asura_Mem::Set(pxToDelete, 0xcd, sizeof(ToDeleteStruct));
						pxToDelete->uRowIndex = uRowIndex;
						pxToDelete->pusBlockId = pxOutgoingEventHeaderRow->GetBlockId();
						Asura_StaticMessage_System::ConvertIDToIndex(*pxToDelete->pusBlockId, &pxToDelete->iBlockIndex, &pxToDelete->eSet);
						pxToDelete->iEventId = pxOutgoingEventHeaderRow->GetEventId();
						pxToDelete->iMessageIndex = -1;
						xToDeleteList.add_entry(pxToDelete);
					}
					else
					{
						Tools_Timed_Message_System::AddMessage (0xff0000, "Cannot delete custom message block.");
					}

					break;
				}
				case Asura_MessageTableRow::TYPE_INCOMING_MESSAGE:		break;
			}
		}
	}

	// Delete all the queued up items

	for(CLIB<ToDeleteStruct*> xIter(&xToDeleteList); !xIter.done(); xIter.next())
	{
		ToDeleteStruct* pxToDelete = xIter();

		SetModified(pxToDelete->pusBlockId);//Flag as modified before deleting ,to make it easier to detect which set this is from

		if(pxToDelete->iMessageIndex == -1) // This is a block header row being deleted
		{
			if(s_pxReferenceToolsObject)
			{
				Asura_StaticMessage_System::DeleteMessageBlock(*pxToDelete->pusBlockId);
				s_pxReferenceToolsObject->RemoveEventMessageBlock(pxToDelete->iEventId);

				// Patch up block ids (as RemoveEventMessageBlock recreates the array)

				CLIB<ToDeleteStruct*> xIter2(xIter);
				xIter2.next();

				for(; !xIter2.done(); xIter2.next())
				{
					ToDeleteStruct* pxToDelete2 = xIter2();

					int iNewBlockId = Asura_StaticMessage_System::ConvertIndexToID(pxToDelete2->eSet, pxToDelete2->iBlockIndex);

					const Asura_EventAndMessage* paxEvents = s_pxReferenceToolsObject->GetServerEntity()->GetEvents();
					int iNumExternalEvents = s_pxReferenceToolsObject->GetServerEntity()->GetNumEvents();

					bool bFound = false;

					for(int n = 0; n < iNumExternalEvents; n++)
					{
						if(paxEvents[n].m_usMessageBlockID == iNewBlockId)
						{
							pxToDelete2->pusBlockId = (u_short*) (&paxEvents[n].m_usMessageBlockID);
							bFound = true;
							break;
						}
					}

					Asura_Assert(bFound, "Could not find message id when patching up event array");
				}
			}
		}
		else // This is a message being deleted
		{
			Asura_StaticMessage_System::DeleteMessage(pxToDelete->pusBlockId, pxToDelete->iMessageIndex);
		}

		Tools_Object_System::CheckForMessageBlockOwnershipProblems();

		delete pxToDelete;
	}
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

void Tools_MessageEditor::DeleteTriggerObjectMessage()
{
	Asura_List<Asura_MessageTableRow>& xRows = s_paxMessageTables[TAB_OUTGOING_MESSAGES]->GetRows();

	Asura_ServerEntity_Trigger* pxTrigger = static_cast<Asura_ServerEntity_Trigger*> (Asura_ServerEntity_Trigger::FindEntity(s_pxReferenceObject->GetGuid()));

	Asura_List_It<Asura_MessageTableRow> xIter(xRows);

	List<ToDeleteStruct*> xToDeleteList;

	for(; !xIter.Done(); xIter.Next())
	{
		if(xIter.GetCurrent()->IsSelected())
		{
			Asura_MessageTableRow* pxSelectedRow = xIter.GetCurrent();

			switch(pxSelectedRow->GetType())
			{
				case Asura_MessageTableRow::TYPE_OUTGOING_MESSAGE:
				{
					Asura_MessageTableRow_Outgoing* pxOutgoingMessageRow = static_cast<Asura_MessageTableRow_Outgoing*> (pxSelectedRow);

					Asura_StaticMessage* pxMessage = pxOutgoingMessageRow->GetOutgoingMessagePointer();

					// Find the message index

					bool bFound = false;

					/*
					const u_short ausInternalIds[5]=
						{
							ASURA_ENTITY_EVENT_ONBEINGTRIGGERED,
							ASURA_ENTITY_EVENT_ONACTIVATION,
							ASURA_ENTITY_EVENT_ONDEACTIVATION,
							ASURA_ENTITY_EVENT_ONDESTRUCTION,
							ASURA_ENTITY_EVENT_ONUNTRIGGER
						};
					*/

					const u_short aeTriggerStateIds[5]=
						{
							ASURA_TRIGGER_ENTITY_ONACTIVATION,
							ASURA_TRIGGER_ENTITY_ONDEACTIVATION,
							ASURA_TRIGGER_ENTITY_ONBEINGTRIGGERED,
							ASURA_TRIGGER_ENTITY_ONDESTRUCTION,
							ASURA_TRIGGER_ENTITY_ONUNTRIGGER,
						};

					for(int i = 0; i < 5; i++)
					{
						//const u_short* pusEventId = &ausInternalIds[i];

						u_short* pusMessageBlock = pxTrigger->GetMessageBlockPointer(aeTriggerStateIds[i]);
						int iNumMessagesInBlock = Asura_StaticMessage_System::GetNumberOfMessagesInBlock(*pusMessageBlock);

						//const char* pszEventName = Tools_Message_Editing::GetEventDescription(*pusEventId);

						//CreateOutgoingEventRow(*pusEventId, pszEventName, pusMessageBlock, iNumMessagesInBlock);

						for(int n = 0; n < iNumMessagesInBlock; n++)
						{
							Asura_StaticMessage* pxMessage2 = Asura_StaticMessage_System::GetMessagePointer(*pusMessageBlock, n);

							if(pxMessage == pxMessage2)
							{
								ToDeleteStruct* pxToDelete = new ToDeleteStruct;
								pxToDelete->pusBlockId = pusMessageBlock;
								pxToDelete->iMessageIndex = n;
								xToDeleteList.add_entry(pxToDelete);

								bFound = true;
								break;
							}
						}

						if(bFound)
						{
							break;
						}
					}

					if(!bFound)
					{
						// Check for custom events

						int iCustomEventIndex;

						if(IsCustomEventRow(pxOutgoingMessageRow, iCustomEventIndex, false))
						{
							ToDeleteStruct* pxToDelete = new ToDeleteStruct;
							pxToDelete->pusBlockId = s_pxReferenceEntity->GetCustomEvent(iCustomEventIndex);
							pxToDelete->iMessageIndex = pxOutgoingMessageRow->GetMessageIndex();
							xToDeleteList.add_entry(pxToDelete);

							bFound = true;
						}
					}

					break;
				}
				/* We can't remove trigger event message blocks, as they've been hardcoded for some reason.
				case Asura_MessageTableRow::TYPE_OUTGOING_EVENT_HEADER:
				{
					Asura_MessageTableRow_OutgoingEventHeader* pxOutgoingEventHeaderRow = static_cast<Asura_MessageTableRow_OutgoingEventHeader*> (pxSelectedRow);
					//Asura_StaticMessage_System::DeleteMessageBlock(*pxOutgoingEventHeaderRow->GetBlockId());
					//s_pxReferenceToolsObject->RemoveEventMessageBlock(*pxOutgoingEventHeaderRow->GetBlockId());

					ToDeleteStruct* pxToDelete = new ToDeleteStruct;
					pxToDelete->pusBlockId = pxOutgoingEventHeaderRow->GetBlockId();
					pxToDelete->iEventId = pxOutgoingEventHeaderRow->GetEventId();
					pxToDelete->iMessageIndex = -1;
					xToDeleteList.add_entry(pxToDelete);

					break;
				}
				*/
				case Asura_MessageTableRow::TYPE_INCOMING_MESSAGE:		break;
			}
		}
	}

	// Delete all the queued up items

	for(CLIB<ToDeleteStruct*> xIter(&xToDeleteList); !xIter.done(); xIter.next())
	{
		ToDeleteStruct* pxToDelete = xIter();

		SetModified(pxToDelete->pusBlockId);//Flag as modified before deleting ,to make it easier to detect which set this is from
		if(pxToDelete->iMessageIndex == -1) // This is a block header row being deleted
		{
			if(s_pxReferenceToolsObject)
			{
				Asura_StaticMessage_System::DeleteMessageBlock(*pxToDelete->pusBlockId);
				s_pxReferenceToolsObject->RemoveEventMessageBlock(pxToDelete->iEventId);
			}
		}
		else // This is a message being deleted
		{
			Asura_StaticMessage_System::DeleteMessage(pxToDelete->pusBlockId, pxToDelete->iMessageIndex);
		}

		delete pxToDelete;
	}
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

void Tools_MessageEditor::ChangeMessageTarget()
{
	if(s_pxReferenceObject == NULL)
	{
		return;
	}

	if(s_bInMessageEditingMode)
	{
		return;
	}

	if(s_pxTabDialog->GetSelected() != TAB_OUTGOING_MESSAGES)
	{
		return;
	}

	Asura_List<Asura_MessageTableRow>& xRows = s_paxMessageTables[TAB_OUTGOING_MESSAGES]->GetRows();

	Asura_List_It<Asura_MessageTableRow> xIter(xRows);

	int iCounter = 0;

	for(; !xIter.Done(); xIter.Next())
	{
		if(xIter.GetCurrent()->IsSelected())
		{
			iCounter++;
		}
	}

	if(iCounter == 0)
	{
		Tools_Timed_Message_System::AddMessage(0xff0000, "No rows selected!");
		return;
	}

	s_pxOverlay->SetOverrideOverlaysHiddenFlag(true);

	Tools_ChooseMessageTargetObject* pxChooseObject = new Tools_ChooseMessageTargetObject(s_pxReferenceObject, s_paxMessageTables[TAB_OUTGOING_MESSAGES]);
	Tools_Object_System::ChooseObject("Please select a new target object for this message.", pxChooseObject);
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

void Tools_MessageEditor::GotoTarget()
{
	if(s_pxOverlay == NULL)
	{
		return;
	}

	Asura_List<Asura_MessageTableRow>& xRows = s_paxMessageTables[TAB_OUTGOING_MESSAGES]->GetRows();

	Asura_List_It<Asura_MessageTableRow> xIter(xRows);

	for(; !xIter.Done(); xIter.Next())
	{
		Asura_MessageTableRow* pxRow = xIter.GetCurrent();

		if(pxRow->IsSelected())
		{
			if(pxRow->GetType() == Asura_MessageTableRow::TYPE_OUTGOING_MESSAGE)
			{
				Asura_MessageTableRow_Outgoing* pxOutgoingMessageRow = static_cast<Asura_MessageTableRow_Outgoing*> (pxRow);
				Asura_StaticMessage* pxMessage = pxOutgoingMessageRow->GetOutgoingMessagePointer();

				Tools_Object* pxObject = pxObject = Tools_Object_System::GetObjectFromGuid(pxMessage->m_uSendTo, true);

				if(pxObject)
				{
					Tools_Object_System::GotoObject(pxObject);
				}
				else
				{
					Tools_Timed_Message_System::AddMessage (0xffff00, "Warning!  Target message not found!");
				}

				break;
			}
		}
	}
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

void Tools_MessageEditor::EditMessageCallback()
{
	if(s_pxOverlay == NULL)
	{
		return;
	}

	Asura_List<Asura_MessageTableRow>& xRows = s_paxMessageTables[TAB_INCOMING_MESSAGES]->GetRows();

	Asura_List_It<Asura_MessageTableRow> xIter(xRows);

	for(; !xIter.Done(); xIter.Next())
	{
		Asura_MessageTableRow* pxRow = xIter.GetCurrent();

		if(pxRow->IsSelected())
		{
			if(pxRow->GetType() == Asura_MessageTableRow::TYPE_INCOMING_MESSAGE)
			{
				Asura_MessageTableRow_Incoming* pxIncomingMessageRow = static_cast<Asura_MessageTableRow_Incoming*> (pxRow);

				Tools_MessageEditor_ReceivedMessageData* pxMessageData = pxIncomingMessageRow->GetReceivedMessageData();

				Tools_Entity_Object* pxObject = static_cast<Tools_Entity_Object*> (Tools_Entity_Object::FindEntity(pxMessageData->m_uSender));

				if(!pxObject)
				{
					Tools_Timed_Message_System::AddMessage(0xffff00, "Failed to find source entity");
					return;
				}

				pxMessageData->m_pxStaticMessage->m_uToolsFlags |= uTOOLS_STATICMESSAGE_FLAG_MESSAGEEDITOR_SELECTED;

				Tools_Object_System::SelectObject(pxObject);
				SelectTab(TAB_OUTGOING_MESSAGES);

				s_pxOverlay->m_bSortToFront = true;

				break;
			}
		}
	}
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

void Tools_MessageEditor::GotoSource()
{
	if(s_pxOverlay == NULL)
	{
		return;
	}

	Asura_List<Asura_MessageTableRow>& xRows = s_paxMessageTables[TAB_INCOMING_MESSAGES]->GetRows();

	Asura_List_It<Asura_MessageTableRow> xIter(xRows);

	for(; !xIter.Done(); xIter.Next())
	{
		Asura_MessageTableRow* pxRow = xIter.GetCurrent();

		if(pxRow->IsSelected())
		{
			if(pxRow->GetType() == Asura_MessageTableRow::TYPE_INCOMING_MESSAGE)
			{
				Asura_MessageTableRow_Incoming* pxIncomingMessageRow = static_cast<Asura_MessageTableRow_Incoming*> (pxRow);

				Tools_MessageEditor_ReceivedMessageData* pxMessageData = pxIncomingMessageRow->GetReceivedMessageData();

				if(pxMessageData->m_xStaticMessageID.GetLocation() == TOOLS_STATICMESSAGELOCATION_DIALOGUE)
				{
					//Source is a dialogue event , so open up the dialogue event editing overlay. RAR 02/03/2009
					Asura_Hash_ID uDialogueEvent = pxMessageData->m_xStaticMessageID.GetDialogueEvent();
					Tools_DialogueEvent_Overlay::EditEvent(uDialogueEvent);
				}
				else
				{
					Tools_Entity_Object* pxObject = static_cast<Tools_Entity_Object*> (Tools_Entity_Object::FindEntity(pxMessageData->m_uSender));

					if(pxObject)
					{
						Tools_Object_System::GotoObject(pxObject);
					}
					else
					{
						Tools_Timed_Message_System::AddMessage(0xffff00, "Failed to find source entity");
					}
				}

				break;
			}
		}
	}
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

void Tools_MessageEditor::SelectTarget()
{
	if(s_pxOverlay == NULL)
	{
		return;
	}

	Asura_List<Asura_MessageTableRow>& xRows = s_paxMessageTables[TAB_OUTGOING_MESSAGES]->GetRows();

	Asura_List_It<Asura_MessageTableRow> xIter(xRows);

	for(; !xIter.Done(); xIter.Next())
	{
		Asura_MessageTableRow* pxRow = xIter.GetCurrent();

		if(pxRow->IsSelected())
		{
			if(pxRow->GetType() == Asura_MessageTableRow::TYPE_OUTGOING_MESSAGE)
			{
				Asura_MessageTableRow_Outgoing* pxOutgoingMessageRow = static_cast<Asura_MessageTableRow_Outgoing*> (pxRow);
				Asura_StaticMessage* pxMessage = pxOutgoingMessageRow->GetOutgoingMessagePointer();

				Tools_Object* pxObject = pxObject = Tools_Object_System::GetObjectFromGuid(pxMessage->m_uSendTo, true);

				if(pxObject)
				{
					Tools_Object_System::SelectObject(pxObject);
				}
				else
				{
					Tools_Timed_Message_System::AddMessage(0xffff00, "Failed to find target");
				}

				break;
			}
		}
	}
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

void Tools_MessageEditor::SelectSource()
{
	if(s_pxOverlay == NULL)
	{
		return;
	}

	Asura_List<Asura_MessageTableRow>& xRows = s_paxMessageTables[TAB_INCOMING_MESSAGES]->GetRows();

	Asura_List_It<Asura_MessageTableRow> xIter(xRows);

	for(; !xIter.Done(); xIter.Next())
	{
		Asura_MessageTableRow* pxRow = xIter.GetCurrent();

		if(pxRow->IsSelected())
		{
			if(pxRow->GetType() == Asura_MessageTableRow::TYPE_INCOMING_MESSAGE)
			{
				Asura_MessageTableRow_Incoming* pxIncomingMessageRow = static_cast<Asura_MessageTableRow_Incoming*> (pxRow);

				Tools_MessageEditor_ReceivedMessageData* pxMessageData = pxIncomingMessageRow->GetReceivedMessageData();

				if(pxMessageData->m_xStaticMessageID.GetLocation() == TOOLS_STATICMESSAGELOCATION_DIALOGUE)
				{
					//Source is a dialogue event , so open up the dialogue event editing overlay. RAR 02/03/2009
					Asura_Hash_ID uDialogueEvent = pxMessageData->m_xStaticMessageID.GetDialogueEvent();
					Tools_DialogueEvent_Overlay::EditEvent(uDialogueEvent);
				}
				else
				{
					Tools_Object* pxObject = pxObject = Tools_Object_System::GetObjectFromGuid(pxMessageData->m_uSender, true);

					if(pxObject)
					{
						Tools_Object_System::SelectObject(pxObject);
					}
					else
					{
						Tools_Timed_Message_System::AddMessage(0xffff00, "Failed to find source entity");
					}
				}

				break;
			}
		}
	}
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

u_short* Tools_MessageEditor::CreateOutputBlock(const char* pszMessageBlockName /* = NULL */)
{
	u_short* pusMsgId = NULL;

	if(s_pxReferenceObject == NULL)
	{
		return NULL;
	}

	if(s_pxTabDialog->GetSelected() == TAB_OUTGOING_MESSAGES)
	{
		// Is this a trigger object?
		Asura_ServerEntity_Trigger* pxTrigger = static_cast<Asura_ServerEntity_Trigger*> (Asura_ServerEntity_Trigger::FindEntity(s_pxReferenceObject->GetGuid()));

		if(pxTrigger)
		{
			Tools_Entity_Trigger_Object* pxToolsTrigger = static_cast<Tools_Entity_Trigger_Object*> (Tools_Entity_Trigger_Object::FindEntity(s_pxReferenceObject->GetGuid()));

			pusMsgId = pxToolsTrigger->AddCustomEvent();

			if(!pusMsgId)
			{
				ShowMessageOverlay("Sorry, this trigger object cannot have additional events assigned.");
			}
			else
			{
				Tools_SaveAsura::SetEntityModified(true);
			}
		}
		else
		{
			// Normal object

			Asura_Entity_Server* pxObject = static_cast<Asura_Entity_Server*> (Asura_Entity_Server::FindEntity(s_pxReferenceObject->GetGuid()));
			Tools_Entity_Object* pxToolsObject = static_cast<Tools_Entity_Object*> (Tools_Entity_Object::FindEntity(s_pxReferenceObject->GetGuid()));

			//The selected object may not be an entity , in which case we need to abort here.
			if(!pxObject || !pxToolsObject)
			{
				Tools_Timed_Message_System::AddMessage (0xffff00, "Can't add any events to this object.");
				return NULL;
			}
						
			int iEventId = RequestEventIdFromUser(pxObject, pxToolsObject);

			if(iEventId == -999)
			{
				pusMsgId = pxToolsObject->AddCustomEvent();
				//Bring the overlay back to the front , since the entity may have re-created its overlay
				Asura_Overlay_System::BringOverlayToTop(TOOLS_OVERLAY_MESSAGE_EDITOR);

			}
			else if(iEventId != -1)
			{
				pxToolsObject->AddEventMessageBlock(iEventId);

				const Asura_EventAndMessage* pxEventAndMessage = pxToolsObject->GetServerEntity()->GetEvents();
				const int iNumEvents = pxToolsObject->GetServerEntity()->GetNumEvents();

				pusMsgId = (u_short*) &pxEventAndMessage[iNumEvents - 1].m_usMessageBlockID;
			}

			if(pusMsgId == NULL)
			{
				Tools_Timed_Message_System::AddMessage (0xffff00, "Warning!  No more events can be added to this object.");
			}
			else
			{
				Tools_SaveAsura::SetEntityModified(true);
			}

			//pxToolsObject->EditMessages(pxObject->GetNumEvents() - 1);

			s_paxMessageTables[TAB_OUTGOING_MESSAGES]->SetDirty();
		}

		s_bForceRecreateTable = true;
	}

	s_pusMessageBlockToSelect = pusMsgId;

	return pusMsgId;
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

int Tools_MessageEditor::RequestEventIdFromUser(Asura_Entity_Server* pxEntity, Tools_Entity_Object* pxToolsEntity)
{
	//get a list of events that this object can handle , but hasn't got a message block for yet
	ListSelection_Overlay xOverlay("Add messages for which event?");

	// If this objects supports custom events (outputs), then add this option first

	if(pxEntity->SupportsCustomEvents())
	{
		xOverlay.AddItem("Custom output", -999);
	}
	
	const Asura_EventAndMessage* pxExistingEvents = pxEntity->GetEvents();
	const int iNumExistingEvents = pxEntity->GetNumEvents();

	const EventIDAndDescription* pxEventList = Tools_Message_Editing::GetEventList();
	if(pxEventList)
	{
		for(const EventIDAndDescription* pxEvent = pxEventList; pxEvent->m_pszDescription; pxEvent++)
		{
			if(!pxToolsEntity->HandlesThisEvent(pxEvent->m_iEventID)) continue;

			//have we a message block for this event?
			bool bEventExists = false;
			for(int i=0;i<iNumExistingEvents;i++)
			{
				if(pxExistingEvents[i].m_usEventID == pxEvent->m_iEventID)
				{
					bEventExists = true;
					break;
				}
			}
			if(!bEventExists)
			{
				xOverlay.AddItem(pxEvent->m_pszDescription,pxEvent->m_iEventID,false,pxEvent->m_szHelpText);
			}
		}
	}

	// if there are any health percent events we can still handle, add the 'On X% health' item
	if(pxToolsEntity->HandlesThisEvent(ASURA_ENTITY_EVENT_ONHEALTHPERCENT_0))
	{
		for(u_int u = 0; u < 100; u++)
		{
			int iEventID = ASURA_ENTITY_EVENT_ONHEALTHPERCENT_0 + u;

			// We assume that if we've got in this function then this object
			// supports all of these events (ie. no objects that support 'on 50%'
			// but not 'on 51%').
			Asura_Assert(pxToolsEntity->HandlesThisEvent(iEventID), "");

			// But still don't let the user add the block twice.
			bool bEventExists = false;
			for(int i = 0; i < iNumExistingEvents; i++)
			{
				if(pxExistingEvents[i].m_usEventID == iEventID)
				{
					bEventExists = true;
					break;
				}
			}
			if(bEventExists) { continue; }

			xOverlay.AddItem("On X% Health", iEventID);
			break;
		}
	}

	if(pxEntity->SupportsCustomEvents() && (xOverlay.GetNumItems() == 1))
	{
		// Our custom event is the only one, so we don't need to ask the user
		return -999;
	}

	if(!xOverlay.GetNumItems())
	{
		//Asura_Assert(0,"No more message blocks can be added to this object");
		return -1;
	}

	if(!xOverlay.DoModal())
	{
		return -1;	
	}

	int iEventID = xOverlay.GetSelectionData();

	// If the 'On X% health' event was selected, spawn a second overlay to set the health
	if( (iEventID >= ASURA_ENTITY_EVENT_ONHEALTHPERCENT_0) && (iEventID <= ASURA_ENTITY_EVENT_ONHEALTHPERCENT_99) )
	{
		bool bValid = false;
		while( !bValid )
		{
			GetNumber_Overlay xHealthOverlay("Select Health Percent",50.0f,0.0f,99.0f,"%2.0f");
			if( !xHealthOverlay.DoModal() ) return -1;

			const int iHealth = Asura_Maths::FloatToInt_Chop( xHealthOverlay.GetNumber() );
			iEventID = ASURA_ENTITY_EVENT_ONHEALTHPERCENT_0 + iHealth;

			// Still don't let the user add the block twice.
			bValid = true;
			for( int i = 0; i < iNumExistingEvents; ++i )
			{
				if( pxExistingEvents[i].m_usEventID == iEventID )
				{
					bValid = false;

					char szTemp[256];
					Asura_PrintfToBfr( szTemp, "An 'On %d%% Health' event already exists for this entity.", 256, true, iHealth );
					Message_Overlay xMsgOverlay( szTemp );
					xMsgOverlay.DoModal();				
				}
			}
		}
	}

	return iEventID;
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

bool Tools_MessageEditor::GetAvailableMessage(Asura_Guid uGuid, Asura_List<MessageIDAndDescription>& xMessageList)
{
	xMessageList.ClearList();

	Tools_Object* pxObject = Tools_Object_System::GetObjectFromGuid(uGuid, true);
	//Tools_Object* pxObject = Tools_Entity_Object::FindEntity(uGuid);

	if(!pxObject)
	{
		return false;
	}

	for(int i=0; i<2; i++)
	{
		const MessageIDAndDescription *pxMessageList = (i == 0) ? Tools_Message_Editing::s_pxThis->GetAsuraMessageList() : Tools_Message_Editing::s_pxThis->GetProjectMessageList();

		if(pxMessageList)
		{
			const MessageIDAndDescription* pxMessageDesc = pxMessageList;

			for(; pxMessageDesc->iID; pxMessageDesc++)
			{
				if(!pxObject->HandlesThisMessage(pxMessageDesc->iID)) continue;

				MessageIDAndDescription* pThisMessage = new MessageIDAndDescription;
				*pThisMessage = *pxMessageDesc;

				xMessageList.AddToList(pThisMessage);
			}
		}
	}

	return true;
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

/*
static int SortRowsFunc(const void* pxVoidLeft, const void* pxVoidRight)
{
	const Tools_RowSortInfo* pxLeft = (const Tools_RowSortInfo*) pxVoidLeft;
	const Tools_RowSortInfo* pxRight = (const Tools_RowSortInfo*) pxVoidRight;

	if(pxLeft->fDelay < pxRight->fDelay)	return -1;
	if(pxLeft->fDelay > pxRight->fDelay)	return 1;

	return 0;
}
*/

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

u_int Tools_MessageEditor::GetNumberOfMessagesInEntity(Tools_BaseEntity_Object* pxObject)
{
	u_int uMessageCount = 0;

	if(pxObject && pxObject->SupportsMessageEditor())
	{
		Asura_ServerEntity_SplitterBlock* pxSplitterBlock = static_cast<Asura_ServerEntity_SplitterBlock*> (Asura_ServerEntity_SplitterBlock::FindEntity(pxObject->GetGuid()));

		if(pxSplitterBlock)
		{
			const Asura_SplitterBlockOutput* paxOutputs = pxSplitterBlock->GetOutputs();

			for(u_int n = 0; n < pxSplitterBlock->GetNumberOfOutputs(); n++)
			{
				uMessageCount += Asura_StaticMessage_System::GetNumberOfMessagesInBlock(paxOutputs[n].m_usMessageBlock);
			}
		}
		else
		{
			Asura_ServerEntity_TimelineTrigger* pxTimelineTrigger = static_cast<Asura_ServerEntity_TimelineTrigger*> (Asura_ServerEntity_TimelineTrigger::FindEntity(pxObject->GetGuid()));

			if(pxTimelineTrigger)
			{
				const Asura_TimelineTrigger_MessageBlock* paxOutputs = pxTimelineTrigger->GetMessageBlocks();

				for(u_int n = 0; n < pxTimelineTrigger->GetNumberOfMessageBlocks(); n++)
				{
					uMessageCount += Asura_StaticMessage_System::GetNumberOfMessagesInBlock(paxOutputs[n].m_usMessageBlock);
				}
			}
			else
			{
				const int iNumCustomEvents = pxObject->GetServerEntity()->GetNumCustomEvents();

				for(int n = 0; n < iNumCustomEvents; n++)
				{
					u_short* pusEvent = pxObject->GetServerEntity()->GetCustomEvent(n);

					if(pusEvent)
					{
						uMessageCount += Asura_StaticMessage_System::GetNumberOfMessagesInBlock(*pusEvent);
					}
				}

				Asura_ServerEntity_Trigger* pxTrigger = static_cast<Asura_ServerEntity_Trigger*> (Asura_ServerEntity_Trigger::FindEntity(pxObject->GetGuid()));

				if(pxTrigger)
				{
					const u_short ausInternalIds[5]=
						{
							ASURA_TRIGGER_ENTITY_ONACTIVATION,
							ASURA_TRIGGER_ENTITY_ONDEACTIVATION,
							ASURA_TRIGGER_ENTITY_ONBEINGTRIGGERED,
							ASURA_TRIGGER_ENTITY_ONDESTRUCTION,
							ASURA_TRIGGER_ENTITY_ONUNTRIGGER,
						};

					for(int i = 0; i < 5; i++)
					{
						const u_short* pusEventId = &ausInternalIds[i];

						u_short* pusMessageBlock = pxTrigger->GetMessageBlockPointer(*pusEventId);
						uMessageCount += Asura_StaticMessage_System::GetNumberOfMessagesInBlock(*pusMessageBlock);
					}
				}

				int iNumExternalEvents = pxObject->GetServerEntity()->GetNumEvents();

				const Asura_EventAndMessage* paxEvents = pxObject->GetServerEntity()->GetEvents();

				for(int n = 0; n < iNumExternalEvents; n++)
				{
					uMessageCount += Asura_StaticMessage_System::GetNumberOfMessagesInBlock(paxEvents[n].m_usMessageBlockID);
				}
			}
		}
	}

	return uMessageCount;
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

void Tools_MessageEditor::CreateOutgoingMessagesTable()
{
	s_paxMessageTables[TAB_OUTGOING_MESSAGES]->ClearAllRows();

	if(s_pxReferenceObject && s_pxReferenceObject->SupportsMessageEditor())
	{
		// Is this is a splitter block?

		Asura_ServerEntity_SplitterBlock* pxSplitterBlock = static_cast<Asura_ServerEntity_SplitterBlock*> (Asura_ServerEntity_SplitterBlock::FindEntity(s_pxReferenceObject->GetGuid()));
		Tools_Entity_SplitterBlock_Object* pxToolsSplitterBlock = static_cast<Tools_Entity_SplitterBlock_Object*> (Tools_Entity_SplitterBlock_Object::FindEntity(s_pxReferenceObject->GetGuid()));

		if(pxSplitterBlock)
		{
			s_paxMessageTables[TAB_OUTGOING_MESSAGES]->SetType(Asura_MessageTable_Dialog::TYPE_SPLITTER_BLOCK);

			u_int uNumOutputs = pxSplitterBlock->GetNumberOfOutputs();

			const Asura_SplitterBlockOutput* paxOutputs = pxSplitterBlock->GetOutputs();

			for(u_int n = 0; n < uNumOutputs; n++)
			{
				const Asura_SplitterBlockOutput* pxOutput = &paxOutputs[n];

				Asura_MessageTableRow_OutgoingEventHeader* pxRow = new Asura_MessageTableRow_OutgoingEventHeader(*s_paxMessageTables[TAB_OUTGOING_MESSAGES]);

				if(pxOutput->m_xName.GetLength() > 0)
				{
					pxRow->SetText(pxOutput->m_xName.GetString());
				}
				else
				{
					pxRow->SetText("unnamed");
				}

				pxRow->SetSplitterOutputBlockPointer((Asura_SplitterBlockOutput*)pxOutput);
				pxRow->SetActiveArrayElementPtr(&pxToolsSplitterBlock->m_pbActive[n]);
				pxRow->SetWeight(pxOutput->m_uWeight);
				pxRow->SetIsDisabled(!pxOutput->m_bActive);
				pxRow->SetBlockId((u_short*) &pxOutput->m_usMessageBlock);

				if(/*s_bInMessageEditingMode &&*/ (s_pusMessageBlockToSelect == &pxOutput->m_usMessageBlock))
				{
					pxRow->SetSelected(true);
				}

				s_paxMessageTables[TAB_OUTGOING_MESSAGES]->AddRow(pxRow);

				// Sort the messages in this block by delay
				SortMessages(pxOutput->m_usMessageBlock);

				// Create the message rows
				FixMessageBlockIfEmpty(const_cast<u_short*>(&pxOutput->m_usMessageBlock));
				int iNumMessagesInBlock = Asura_StaticMessage_System::GetNumberOfMessagesInBlock(pxOutput->m_usMessageBlock);

				for(int i = 0; i < iNumMessagesInBlock; i++)
				{
					Asura_MessageTableRow_Outgoing* pxRow2 = CreateOutgoingMessageRow((u_short*) &pxOutput->m_usMessageBlock, i);

					pxRow2->SetSplitterOutputBlockPointer((Asura_SplitterBlockOutput*)pxOutput);
				}
			}
		}
		else
		{
			// Is this is a timeline trigger?

			Asura_ServerEntity_TimelineTrigger* pxTimelineTrigger = static_cast<Asura_ServerEntity_TimelineTrigger*> (Asura_ServerEntity_TimelineTrigger::FindEntity(s_pxReferenceObject->GetGuid()));
			Tools_Entity_TimelineTrigger_Object* pxToolsTimelineTrigger = static_cast<Tools_Entity_TimelineTrigger_Object*> (Tools_Entity_TimelineTrigger_Object::FindEntity(s_pxReferenceObject->GetGuid()));

			if(pxTimelineTrigger)
			{
				s_paxMessageTables[TAB_OUTGOING_MESSAGES]->SetType(Asura_MessageTable_Dialog::TYPE_TIMELINE_TRIGGER);

				u_int uNumOutputs = pxTimelineTrigger->GetNumberOfMessageBlocks();

				const Asura_TimelineTrigger_MessageBlock* paxOutputs = pxTimelineTrigger->GetMessageBlocks();

				for(u_int n = 0; n < uNumOutputs; n++)
				{
					const Asura_TimelineTrigger_MessageBlock* pxOutput = &paxOutputs[n];

					Asura_MessageTableRow_OutgoingEventHeader* pxRow = new Asura_MessageTableRow_OutgoingEventHeader(*s_paxMessageTables[TAB_OUTGOING_MESSAGES]);

					if(pxOutput->m_xName.GetLength() > 0)
					{
						pxRow->SetText(pxOutput->m_xName.GetString());
					}
					else
					{
						pxRow->SetText("unnamed");
					}

					pxRow->SetTimelineTriggerMessageBlock((Asura_TimelineTrigger_MessageBlock*)pxOutput);
					pxRow->SetActiveArrayElementPtr(&pxToolsTimelineTrigger->m_pbActive[n]);
					pxRow->SetDelayArrayElementPtr(&pxToolsTimelineTrigger->m_pfTime[n]);
					pxRow->SetDelay(pxOutput->m_fTime);
					pxRow->SetIsDisabled(!pxOutput->m_bActive);
					pxRow->SetBlockId((u_short*) &pxOutput->m_usMessageBlock);

					if(/*s_bInMessageEditingMode &&*/ (s_pusMessageBlockToSelect == &pxOutput->m_usMessageBlock))
					{
						pxRow->SetSelected(true);
					}

					s_paxMessageTables[TAB_OUTGOING_MESSAGES]->AddRow(pxRow);

					FixMessageBlockIfEmpty(const_cast<u_short*>(&pxOutput->m_usMessageBlock));
					int iNumMessagesInBlock = Asura_StaticMessage_System::GetNumberOfMessagesInBlock(pxOutput->m_usMessageBlock);

					// Sort the messages in this block by delay
					SortMessages(pxOutput->m_usMessageBlock);

					// Create the message rows

					for(int i = 0; i < iNumMessagesInBlock; i++)
					{
						Asura_MessageTableRow_Outgoing* pxRow2 = CreateOutgoingMessageRow((u_short*) &pxOutput->m_usMessageBlock, i);

						pxRow2->SetTimelineTriggerMessageBlock((Asura_TimelineTrigger_MessageBlock*)pxOutput);
					}
				}
			}
			else
			{
				// First check for custom entity events

				int iNumCustomEvents = s_pxReferenceEntity->GetNumCustomEvents();

				for(int n = 0; n < iNumCustomEvents; n++)
				{
					Asura_String xEventName;

					s_pxReferenceEntity->GetCustomEventName(n, xEventName);

					u_short* pusCustomEvent = s_pxReferenceEntity->GetCustomEvent(n);

					Asura_Assert(pusCustomEvent, "Custom event pointer is null");

					if(pusCustomEvent)
					{
						AddEventRow(-1, xEventName, pusCustomEvent);
					}
				}

				// Is this a trigger entity?

				Asura_ServerEntity_Trigger* pxTrigger = static_cast<Asura_ServerEntity_Trigger*> (Asura_ServerEntity_Trigger::FindEntity(s_pxReferenceObject->GetGuid()));

				if(pxTrigger)
				{
					s_paxMessageTables[TAB_OUTGOING_MESSAGES]->SetType(Asura_MessageTable_Dialog::TYPE_NORMAL);

					const u_short ausInternalIds[5]=
						{
							//ASURA_ENTITY_EVENT_ONBEINGTRIGGERED,
							//ASURA_ENTITY_EVENT_ONACTIVATION,
							//ASURA_ENTITY_EVENT_ONDEACTIVATION,
							//ASURA_ENTITY_EVENT_ONDESTRUCTION,
							//ASURA_ENTITY_EVENT_ONUNTRIGGER
							ASURA_TRIGGER_ENTITY_ONACTIVATION,
							ASURA_TRIGGER_ENTITY_ONDEACTIVATION,
							ASURA_TRIGGER_ENTITY_ONBEINGTRIGGERED,
							ASURA_TRIGGER_ENTITY_ONDESTRUCTION,
							ASURA_TRIGGER_ENTITY_ONUNTRIGGER,
						};


					for(int iPass = 0; iPass < 2; iPass++)
					{
						for(int i = 0; i < 5; i++)
						{
							const u_short* pusEventId = &ausInternalIds[i];

							u_short* pusMessageBlock = pxTrigger->GetMessageBlockPointer(*pusEventId);
							FixMessageBlockIfEmpty(pusMessageBlock);
							int iNumMessagesInBlock = Asura_StaticMessage_System::GetNumberOfMessagesInBlock(*pusMessageBlock);

							// First pass shows event blocks with messages, second pass shows event blocks without (for ordering)
							if((iNumMessagesInBlock > 0) && (iPass == 1))
							{
								continue;
							}

							// First pass shows event blocks with messages, second pass shows event blocks without (for ordering)
							if((iNumMessagesInBlock == 0) && (iPass == 0))
							{
								continue;
							}

							const char* pszEventName = Tools_Message_Editing::GetEventDescription(*pusEventId);

							CreateOutgoingEventRow(*pusEventId, pszEventName, pusMessageBlock, iNumMessagesInBlock);

							// Sort the messages in this block by delay
							SortMessages(*pusMessageBlock);

							// Create the message rows

							for(int n = 0; n < iNumMessagesInBlock; n++)
							{
								CreateOutgoingMessageRow(pusMessageBlock, n);
							}
						}
					}
				}

				// Now handle any other entity types (note that triggers will fall through to this section too)

				s_paxMessageTables[TAB_OUTGOING_MESSAGES]->SetType(Asura_MessageTable_Dialog::TYPE_NORMAL);

				const Asura_EventAndMessage* paxEvents = s_pxReferenceEntity->GetEvents();

				int iNumExternalEvents = s_pxReferenceEntity->GetNumEvents();

				for(int n = 0; n < iNumExternalEvents; n++)
				{
					const Asura_EventAndMessage* pxEvent = &paxEvents[n];

					AddEventRow(pxEvent->m_usEventID, Tools_Message_Editing::GetEventDescription(pxEvent->m_usEventID), (unsigned short*)&pxEvent->m_usMessageBlockID);
				}
			}
		}
	}

	// Set up some callbacks

	s_paxMessageTables[TAB_OUTGOING_MESSAGES]->SetRowSelChangeCallback(SelectedRowChangeCallback);

	s_paxMessageTables[TAB_OUTGOING_MESSAGES]->SetDirty();
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

void Tools_MessageEditor::AddEventRow(int iEventId, const Asura_String& xEventName, unsigned short* pusMessageBlockId)
{
	int iNumMessagesInBlock;

	FixMessageBlockIfEmpty(pusMessageBlockId);
	iNumMessagesInBlock = Asura_StaticMessage_System::GetNumberOfMessagesInBlock(*pusMessageBlockId);

	CreateOutgoingEventRow(iEventId, xEventName, (unsigned short *) pusMessageBlockId, iNumMessagesInBlock);

	// Sort the messages in this block by delay
	SortMessages(*pusMessageBlockId);

	// Create the message rows

	for(int i = 0; i < iNumMessagesInBlock; i++)
	{
		CreateOutgoingMessageRow((u_short*) pusMessageBlockId, i);
	}
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

void Tools_MessageEditor::SelectedRowChangeCallback(bool bSelected, Asura_MessageTableRow* pxRow)
{
	if(bSelected)
	{
		switch(pxRow->GetType())
		{
			case Asura_MessageTableRow::TYPE_OUTGOING_MESSAGE:
			{
				if(s_paxMessageTables[TAB_OUTGOING_MESSAGES]->GetNumSelectedRows() == 1)
				{
					Asura_MessageTableRow_Outgoing* pxOutgoingMessageRow = static_cast<Asura_MessageTableRow_Outgoing*> (pxRow);

					Tools_MessageEditor::CreateMessageAttributesOverlay(pxOutgoingMessageRow->GetOutgoingMessagePointer());

					//bring the message editor back to the front
					//(prevents it fading out while changing selection via the arrow keys)
					Asura_Overlay_System::BringOverlayToTop(TOOLS_OVERLAY_MESSAGE_EDITOR);
				}
				else
				{
					// Kill the old message editing overlay
					CloseMessageAttributeOverlay();
				}

				break;
			}

			default:
			{
				// Kill the old message editing overlay
				CloseMessageAttributeOverlay();
				break;
			}
		}
	}

	s_pusCachedBlockIdFromRowSelection = GetBlockIdFromRowSelection(false);
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

void Tools_MessageEditor::CreateOutgoingEventRow(int iEventId, const Asura_String xEventName, u_short* pusMessageBlockId, int iNumMessagesInBlock)
{
	// Get the event name, and add a table row for this event header

	char szEventName [128];

	Asura_PrintfToBfr(szEventName, "%s (%d)", 100, true, xEventName.GetString(), iNumMessagesInBlock);

	Asura_MessageTableRow_OutgoingEventHeader* pxRow = new Asura_MessageTableRow_OutgoingEventHeader(*s_paxMessageTables[TAB_OUTGOING_MESSAGES]);
	pxRow->SetText(szEventName);
	pxRow->SetBlockId(pusMessageBlockId);
	pxRow->SetEventId(iEventId);

	if(/*s_bInMessageEditingMode &&*/ (s_pusMessageBlockToSelect == pusMessageBlockId))
	{
		pxRow->SetSelected(true);
	}

	s_paxMessageTables[TAB_OUTGOING_MESSAGES]->AddRow(pxRow);
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

Asura_MessageTableRow_Outgoing* Tools_MessageEditor::CreateOutgoingMessageRow(u_short *pusMessageBlockId, int iMessageIndex)
{
	Asura_StaticMessage* pxMessage2 = Asura_StaticMessage_System::GetMessagePointer(*pusMessageBlockId, iMessageIndex);

	Asura_String xMessageDescription;

	Tools_Message_Editing::GetMessageDescription(pxMessage2->m_usMsgID, NULL, xMessageDescription);

	//Tools_Entity_Object* pxTargetObject = Tools_Entity_Object::FindEntity(pxMessage2->m_uSendTo);

	Asura_MessageTableRow_Outgoing* pxRow = new Asura_MessageTableRow_Outgoing(*s_paxMessageTables[TAB_OUTGOING_MESSAGES], pxMessage2);
	pxRow->SetDelay(pxMessage2->m_fDelay);
	pxRow->SetProbability(static_cast<u_int> (pxMessage2->m_fProbability * 100.0f));
	pxRow->SetIsDisabled(pxMessage2->IsDisabled());
	//pxRow->SetOutgoingMessagePointer(pxMessage2);
	pxRow->SetBlockId(pusMessageBlockId);
	pxRow->SetTargetText(Tools_Message_Editing::s_pxThis->GetNameFromGuidMain(pxMessage2->m_uSendTo));
	pxRow->SetMessageIndex(iMessageIndex);
	pxRow->SetSelected((pxMessage2->m_uToolsFlags & uTOOLS_STATICMESSAGE_FLAG_MESSAGEEDITOR_SELECTED) == 0 ? false : true);
	s_paxMessageTables[TAB_OUTGOING_MESSAGES]->AddRow(pxRow);

	pxMessage2->m_uToolsFlags &= ~uTOOLS_STATICMESSAGE_FLAG_MESSAGEEDITOR_SELECTED;

	Asura_List<MessageIDAndDescription> xMessageList;

	if(Tools_MessageEditor::GetAvailableMessage(pxMessage2->m_uSendTo, xMessageList))
	{

		int iNumMessages = xMessageList.GetNumEntries();

		if(iNumMessages > 0)
		{
			const char** paszMessageNames = new const char* [iNumMessages];
			u_short* pausMessageIds = new u_short [iNumMessages];

			int iDropdownSelection = -1;

			Asura_List_It<MessageIDAndDescription> xIter(xMessageList);

			for(int iCounter=0; !xIter.Done(); xIter.Next(), iCounter++)
			{
				if(pxMessage2->m_usMsgID == xIter.GetCurrent()->iID)
				{
					iDropdownSelection = iCounter;
				}

				paszMessageNames[iCounter] = xIter.GetCurrent()->pszDescription;
				pausMessageIds[iCounter] = static_cast<u_short> (xIter.GetCurrent()->iID);

				delete xIter.GetCurrent();
			}

			//Asura_Assert(iDropdownSelection != -1, "Could not find current message in available message list.");

			pxRow->SetAvailableMessages(paszMessageNames, pausMessageIds, iNumMessages);

			if(iDropdownSelection == -1)
			{
				// The new target object that the user has selected doesn't support the current message, so reassign it.

				Asura_StaticMessage* pxMessage = pxRow->GetOutgoingMessagePointer();
				pxMessage->m_usMsgID = pausMessageIds[0];
				Tools_Message_Editing::s_pxThis->InitialiseMessageData(pxMessage, false);
				CloseMessageAttributeOverlay();

				Tools_Timed_Message_System::AddMessage (0xff0000, "Warning!  Message-type has been changed due to new target object not supporting the old message-type!!");
			}
			else
			{
				pxRow->SetMessageDropdownIndex(iDropdownSelection);
			}
		}
	}
	else
	{
		// As there is no target object assigned, just show the activate message

		const char** paszMessageNames = new const char* [1];
		u_short* pausMessageIds = new u_short[1];
		pausMessageIds[0] = pxMessage2->m_usMsgID;

		Asura_String xMsgName;

		if(Tools_Message_Editing::GetMessageDescription(pxMessage2->m_usMsgID, pxMessage2, xMsgName))
		{
			char* pszMessageName = new char[xMsgName.GetLength() + 1];
			strcpy(pszMessageName, xMsgName.GetString());

			paszMessageNames[0] = pszMessageName;
		}
		else
		{
			paszMessageNames[0] = "ERROR";
		}

		pxRow->SetAvailableMessages(paszMessageNames, pausMessageIds, 1, true);
		pxRow->SetMessageDropdownIndex(0);
	}

	return pxRow;
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

void Tools_MessageEditor::CreateIncomingMessagesTable()
{
	s_paxMessageTables[TAB_INCOMING_MESSAGES]->ClearAllRows();

	if(s_pxReferenceObject && s_pxReferenceObject->GetGuid() != ASURA_GUID_UNREGISTERED)
	{
		Asura_List<Tools_MessageEditor_ReceivedMessageData> xIncomingMessageList;

		const char** paszMessageStrings = GenerateIncomingMessageList(s_pxReferenceObject, xIncomingMessageList);

		Asura_List_It<Tools_MessageEditor_ReceivedMessageData> xIter(xIncomingMessageList);

		int n = 0;

		for(; !xIter.Done(); xIter.Next(), n++)
		{
			Tools_MessageEditor_ReceivedMessageData* pxReceivedMessageData = xIter.GetCurrent();

			Asura_MessageTableRow_Incoming* pxRow = new Asura_MessageTableRow_Incoming(*s_paxMessageTables[TAB_INCOMING_MESSAGES]);
			pxRow->SetMessageText(paszMessageStrings[n]);
			pxRow->SetSourceText(pxReceivedMessageData->m_xSenderName.GetString());
			pxRow->SetReceivedMessageData(pxReceivedMessageData);
			s_paxMessageTables[TAB_INCOMING_MESSAGES]->AddRow(pxRow);
		}

		delete [] paszMessageStrings;
	}

	s_paxMessageTables[TAB_INCOMING_MESSAGES]->SetDirty();
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

const char** Tools_MessageEditor::GenerateIncomingMessageList(Tools_Object* pxObject, Asura_List<Tools_MessageEditor_ReceivedMessageData>& xIncomingMessageList)
{
	// Search for all messages targeting the reference object

	for(Tools_StaticMessageIterator xIterator(true); !xIterator.Done(); xIterator.Next())
	{
		const Asura_StaticMessage* pxMessage = xIterator.GetCurrent();

		if(pxMessage->m_uSendTo != pxObject->GetGuid()) 
		{
			// (MH - 09.01.2008) is it sent to the entity via a proxy?
			const Asura_ServerEntity_ProxyTrigger* pxProxy = Asura_ServerEntity_ProxyTrigger::FindEntity(pxMessage->m_uSendTo);
			if(!pxProxy || !pxProxy->IsEntityTargeted(pxObject->GetGuid()))
			{
				continue;
			}
		}

		//fill in details for this message
		Tools_MessageEditor_ReceivedMessageData* pxData = new Tools_MessageEditor_ReceivedMessageData;

		xIterator.GetCurrentMessageID(pxData->m_xStaticMessageID);
		pxData->m_bDisabled = pxMessage->IsDisabled();

		pxData->m_iMessageID = pxMessage->m_usMsgID;
		pxData->m_pxStaticMessage = (Asura_StaticMessage*)pxMessage;

		//find the sender of the message
		pxData->m_xStaticMessageID.GetSenderInfo(&pxData->m_uSender, &pxData->m_xSenderName, &pxData->m_xMessageEvent);
		if(pxData->m_xSenderName.IsBlank()) pxData->m_xSenderName = "Unknown";
		if(pxData->m_xMessageEvent.IsBlank()) pxData->m_xMessageEvent = "Unknown";

		pxData->m_uMessageBlockId = pxData->m_xStaticMessageID.GetStaticMessageBlockID();

		Tools_Message_Editing::GetMessageDescription(pxData->m_iMessageID,pxMessage,pxData->m_xMessageDesc);

		if(pxData->m_bDisabled)
		{
			//Alter the description to indicate that the message is disabled
			char szTemp[200];
			Asura_PrintfToBfr(szTemp,"(D) %s",sizeof(szTemp),true,pxData->m_xMessageDesc.GetString());
			pxData->m_xMessageDesc = szTemp;
		}

		xIncomingMessageList.AddToList(pxData);	
	}

	if(!xIncomingMessageList.GetNumEntries()) return NULL;

	//build list of strings for the message overlay
	const char** pszListStrings = new const char*[xIncomingMessageList.GetNumEntries()];
	for(int i=0;i<xIncomingMessageList.GetNumEntries();i++)
	{
		pszListStrings[i] = xIncomingMessageList.GetEntryByIndex(i)->m_xMessageDesc.GetString();
	}

	return pszListStrings;
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

void Tools_MessageEditor::RecreateTable()
{
	switch(s_pxTabDialog->GetSelected())
	{
		case TAB_OUTGOING_MESSAGES:		CreateOutgoingMessagesTable();		break;
		case TAB_INCOMING_MESSAGES:		CreateIncomingMessagesTable();		break;
	}
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

bool Tools_ChooseMessageTargetObject::CanBeSelected(Tools_Object* pxObject)
{
	Asura_Assert(pxObject, "Null tools object");
	if(!pxObject) return false;

	/*
	if(pxObject == m_pxSourceObject)
	{
		return false;
	}
	*/

	switch(pxObject->Type)
	{
		case TOT_Phonon:
		case TOT_StreamingBackgroundSound:
		{
			return true;
		}
	}

	//allow object if its derived physical object , or if it's the player
	const Asura_Guid uGuid = pxObject->GetGuid();
	if(uGuid == ASURA_GUID_UNREGISTERED) return false;

	// Add all the special guids except for a few that obviously make no sense.
	if(pxObject && pxObject->Type == TOT_SpecialGuid)
	{
		if(uGuid == ASURA_GUID_SERVER ||
			uGuid == ASURA_GUID_ALL_CLIENTMACHINES ||
			uGuid == ASURA_GUID_DIALOGUE_SPEAKER)
		{
			return false;
		}
		return true;
	}

	//if(Asura_ServerEntity_PhysicalObject::FindEntity(uGuid))
	{
		return true;
	}

	//return false;
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

void Tools_ChooseMessageTargetObject::ObjectSelected(Tools_Object* pxObject)
{
	if(Tools_MessageEditor::GetReferenceObject() && pxObject)
	{
		//Prevent targeting entities in other files if it is not safe to do so. RAR 20/02/2009
		if(!Tools_SecondaryEntityFile_System::IsAllowableTarget(Tools_MessageEditor::GetReferenceObject(), pxObject, true))
		{
			return;
		}
	}
	if(!pxObject) return;

	Asura_List<Asura_MessageTableRow>& xRows = m_pxMessageTable->GetRows();

	Asura_List_It<Asura_MessageTableRow> xIter(xRows);

	for(; !xIter.Done(); xIter.Next())
	{
		Asura_MessageTableRow* pxRow = xIter.GetCurrent();

		if(pxRow->IsSelected())
		{
			if(pxRow->GetType() == Asura_MessageTableRow::TYPE_OUTGOING_MESSAGE)
			{
				Asura_MessageTableRow_Outgoing* pxOutgoingMessageRow = static_cast<Asura_MessageTableRow_Outgoing*> (pxRow);
				Asura_StaticMessage* pxMessage = pxOutgoingMessageRow->GetOutgoingMessagePointer();

				pxMessage->m_uSendTo = pxObject->GetGuid();
				Tools_MessageEditor::SetModified(pxRow->GetBlockId());
			}
		}
	}

	Tools_MessageEditor::RecreateTable();

	m_pxMessageTable->SetDirty();
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

void Tools_MessageEditor::CreateMessageAttributesOverlay(Asura_StaticMessage* pxMessage)
{
	if(!pxMessage)
	{
		Asura_Assert(pxMessage, "ChangeMessageAttributesOverlay() - Not currently editing a message using this system");
		return;
	}

	//kill the old message editing overlay
	CloseMessageAttributeOverlay();

	// Don't show if more than one message is selected
	if(s_paxMessageTables[s_uSelectedTab])
	{
		if(s_paxMessageTables[s_uSelectedTab]->GetNumSelectedRows() > 1)
		{
			return;
		}
	}

	if(pxMessage != s_pxMessageEditedByAttributeOverlay)
	{
		//take a backup copy of the edited message
		s_xMessageBackup.Copy(pxMessage);
	}

	s_pxMessageEditedByAttributeOverlay = pxMessage;
 
	Asura_Overlay* pxOverlay;
	Asura_String xMessageDesc;
	Tools_Message_Editing::GetMessageDescription(pxMessage->m_usMsgID, 0, xMessageDesc);

	pxOverlay = new Asura_Overlay(xMessageDesc.GetString(), false, &s_xMessageAttributeOverlayPosition);

	Asura_Vector_2 xOffset = Asura_Vector_2(1.0f, 2.0f);

	//Disable message option
	Asura_Dialog* pxDialog = new Asura_FlagTickbox_Dialog("Disabled",xOffset,&pxMessage->m_uToolsFlags,uTOOLS_STATICMESSAGE_FLAG_DISABLED);
	pxDialog->SetHelpText("Disables this messages for testing/debugging purposes.\nMessage flagged in this way won't be included in level exports.");
	xOffset.y += Asura_Dialog::DefaultSpacing();
	pxOverlay->AddDialog(pxDialog);

	//time delay is available for all messages
	pxDialog = new Asura_Numerical_Dialog("Delay",xOffset,&pxMessage->m_fDelay,0,9999.0f,0.1f,"%.3f");
	pxDialog->SetHelpText("Time delay for message in seconds");
	xOffset.y += Asura_Dialog::DefaultSpacing();
	pxOverlay->AddDialog(pxDialog);

	pxDialog = new Asura_ScaledNumerical_Dialog("Probability",xOffset,100.0f,&pxMessage->m_fProbability,0,100.0f,1.0f," %.2f ");
	pxDialog->SetHelpText("Percentage chance of the message being sent.");
	xOffset.y += Asura_Dialog::LargeSpacing();
	pxOverlay->AddDialog(pxDialog);

	Tools_Message_Editing::s_pxThis->AddOverlayControls(pxMessage,pxOverlay,xOffset);

	pxOverlay->m_uID = TOOLS_OVERLAY_MESSAGEEDIT;

	Asura_Overlay_System::SubmitNewOverlay(pxOverlay,false);
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

bool Tools_ChooseAddMessageTargets::CanBeSelected(Tools_Object* pxObject)
{
	Asura_Assert(pxObject, "Null tools object");
	if(!pxObject) return false;

	/*
	if(pxObject == m_pxSourceObject)
	{
		return false;
	}
	*/

	switch(pxObject->Type)
	{
		case TOT_Phonon:
		case TOT_StreamingBackgroundSound:
		case TOT_AttractorNode:
		{
			return true;
		}
	}

	//allow object if its derived physical object , or if it's the player
	const Asura_Guid uGuid = pxObject->GetGuid();
	if(uGuid == ASURA_GUID_UNREGISTERED) return false;

	// Add all the special guids except for a few that obviously make no sense.
	if(pxObject && pxObject->Type == TOT_SpecialGuid)
	{
		/*
		if(uGuid == ASURA_GUID_SERVER ||
			uGuid == ASURA_GUID_ALL_CLIENTMACHINES ||
			uGuid == ASURA_GUID_DIALOGUE_SPEAKER)
		{
			return false;
		}
		*/
		return true;
	}

	//if(Asura_ServerEntity_PhysicalObject::FindEntity(uGuid))
	{
		return true;
	}

	//return false;
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

void Tools_ChooseAddMessageTargets::ObjectSelected(Tools_Object* pxObject)
{
	const List<Tools_Object*>* pxSelectionList = GetSelectionList();

	Asura_List<Asura_Guid> xSelectionList;

	if(pxObject)
	{
		int iNumDisallowed = 0;
		bool bSingleTarget = pxSelectionList->size() == 1;
		for(CLIF<Tools_Object*> xIter(pxSelectionList); !xIter.done(); xIter.next())
		{
			Tools_Object* pxSelectedObject = xIter();

			Asura_Guid xGuid = pxSelectedObject->GetGuid();

			if(xGuid == ASURA_GUID_UNREGISTERED)
			{
				if(!pxSelectedObject->AcquireGuid())
				{
					Tools_Timed_Message_System::AddMessage(0xff0000, "That object isn't a valid target for messages.");
					continue;
				}
			}
			if(Tools_MessageEditor::GetReferenceObject())
			{
				//Prevent targeting entities in other files if it is not safe to do so. RAR 20/02/2009
				if(!Tools_SecondaryEntityFile_System::IsAllowableTarget(Tools_MessageEditor::GetReferenceObject(), pxSelectedObject, bSingleTarget))
				{
					iNumDisallowed++;
					continue;
				}
			}

			Asura_Guid* pxGuid = new Asura_Guid;
			*pxGuid = pxSelectedObject->GetGuid();
			xSelectionList.AddToList(pxGuid);
		}
		
		if(iNumDisallowed && !bSingleTarget)
		{
			//Had multiple objects selected , so combine all the rejections into one error message
			ShowMessageOverlay("%d object(s) were rejected because they were in a different file , and not yet safe to target.", iNumDisallowed);
		}

	}
	else
	{
		Tools_Timed_Message_System::AddMessage(0xff0000, "No object selected - please select special GUID");

		Asura_Guid uGuid;

		// Modal overlay here for GUID selection
		if(!Tools_Message_Editing::EditDestinationGuid(uGuid, "Select Destination Guid"))
		{
			return;
		}

		Asura_Guid* pxGuid = new Asura_Guid(uGuid);
		*pxGuid = uGuid;
		xSelectionList.AddToList(pxGuid);
	}

	if(!xSelectionList.IsEmpty())
	{
		Asura_StaticMessage xMessage;

		if(Tools_Message_Editing::s_pxThis->EditMessageID(&xMessage, "Select message to send", xSelectionList))
		{
			for(Asura_List_It<Asura_Guid> xIter(xSelectionList); !xIter.Done(); xIter.Next())
			{
				Tools_MessageEditor::AddMessage(xMessage.m_usMsgID, *(xIter.GetCurrent()));
			}

			if(xSelectionList.GetNumEntries() > 1)
			{
				// If we've added multiple messages, then we don't want to show the message attribute overlay
				Tools_MessageEditor::ResetOverlayToOpenPointer();
			}
		}
	}
	
	for(Asura_List_It<Asura_Guid> xIter(xSelectionList); !xIter.Done(); xIter.Next())
	{
		delete xIter.GetCurrent();
	}
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

void Tools_ChooseAddMessageTargets::OnFinish ()
{
	Tools_MessageEditor::StopMessageEditingMode();
	delete this;
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

void Tools_MessageEditor::UpdateObjectOverlay()
{
	//Limit this to updating overlays for entity objects for now  , to avoid
	//problems when using this within cutscene mode.
	//RAR 29/04/2009
	if(s_pxReferenceToolsObject)
	{ 
		s_pxReferenceToolsObject->CreateObjectOverlay();
	}

	if(s_pxOverlay)
	{
		s_pxOverlay->m_bSortToFront = true;
	}
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

void Tools_MessageEditor::HandleDeletedObject(Tools_Object* pxObject)
{
	if(pxObject == s_pxReferenceObject)
	{
		s_pxReferenceObject = NULL;
		s_pxReferenceToolsObject = NULL;
		s_pxReferenceEntity = NULL;

		s_bForceRecreateTable = true;
	}
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

void Tools_MessageEditor::SelectTab(TabNamesEnum eTab)
{
	s_pxTabDialog->SelectTab(eTab);

	for(u_int n = 0; n < NUM_TABS; n++)
	{
		if(s_paxMessageTables[n])
		{
			/*
			if(s_paxMessageTables[n]->IsTableVisible())
			{
				s_paxMessageTables[n]->PerFrameUpdate();
			}
			*/

			s_paxMessageTables[n]->SetTableVisible(false);
		}
	}

	if(s_paxMessageTables[eTab])
	{
		s_paxMessageTables[eTab]->SetTableVisible(true);
	}
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

Asura_StaticMessageSetIndex Tools_MessageEditor::GetMessageSetFromBlockId(const u_short* pusBlockID)
{
	//default to the entity set
	Asura_StaticMessageSetIndex eSet = ASURA_STATICMESSAGE_ENTITYSET;
	
	if(!s_pxReferenceEntity)
	{
		Asura_Assert(s_pxReferenceEntity, "");
		return eSet;
	}
	
	if(!pusBlockID)
	{
		Asura_Assert(pusBlockID, "");
		return eSet;
	}	
	
	//check to see if this is a custom event , and if so , if it should go in the cutscene file
	Asura_Entity_Server* pxEntity = s_pxReferenceEntity;
	
	if(pxEntity->SupportsCustomEvents())
	{
		for(int i=0; i<pxEntity->GetNumCustomEvents(); i++)
		{
			if(pusBlockID == pxEntity->GetCustomEvent(i))
			{
				eSet = (Asura_StaticMessageSetIndex)pxEntity->GetCustomEventMessageSet(i);
				break;
			}
		}
	}

	//If the message block isn't empty , then confirm that the set being used is the one we expected
	if(*pusBlockID != ASURA_STATICMESSAGE_EMPTYBLOCK)
	{
		int iBlockIndex;
		Asura_StaticMessageSetIndex eActualSet;
		Asura_StaticMessage_System::ConvertIDToIndex(*pusBlockID, &iBlockIndex, &eActualSet);
		Asura_Assertf(eActualSet == eSet, ("Message block belongs to the wrong static message set/file.\nExpected Set %d\nActual Set %d",eSet, eActualSet));
	}
	return eSet;
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

void Tools_MessageEditor::SetModified(const u_short* pusBlockID)
{
	if(!pusBlockID)
	{
		Asura_Assert(pusBlockID, "");
		return;
	}

	//find the set that the message block is supposed to be in , and flag that file as modified
	Asura_StaticMessageSetIndex eSet = GetMessageSetFromBlockId(pusBlockID);
	switch(eSet)
	{
		case ASURA_STATICMESSAGE_ENTITYSET :
			Tools_SaveAsura::SetEntityModified(true);
			break;
		case ASURA_STATICMESSAGE_CUTSCENESET :
			Tools_SaveAsura::SetCutscenesModified(true);
			break;
		default :
			Asura_Assert(0,"Unexpected message block set");
	}

	//Find the set that the message block is actually in , and flag that as well
	if(*pusBlockID != ASURA_STATICMESSAGE_EMPTYBLOCK)
	{
		int iBlockIndex;
		Asura_StaticMessageSetIndex eActualSet;
		Asura_StaticMessage_System::ConvertIDToIndex(*pusBlockID, &iBlockIndex, &eActualSet);
		if(eSet != eActualSet)
		{
			switch(eActualSet)
			{
				case ASURA_STATICMESSAGE_ENTITYSET :
					Tools_SaveAsura::SetEntityModified(true);
					break;
				case ASURA_STATICMESSAGE_CUTSCENESET :
					Tools_SaveAsura::SetCutscenesModified(true);
					break;
				default :
					Asura_Assert(0,"Unexpected message block set");
			}
		}
	}

	if(s_pxReferenceToolsObject && eSet == ASURA_STATICMESSAGE_ENTITYSET)
	{
		//Flag the entity as modified , so the correct secondary entity file gets flagged as well
		s_pxReferenceToolsObject->SetEntityModified();
	}
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

void Tools_MessageEditor::SetModified(const Asura_StaticMessage* pxMessage)
{
	if(!pxMessage)
	{
		Asura_Assert(pxMessage, "");
		return;
	}

	//Search over all messages to find out where this lives , so the appropriate file can be flagged
	//as modified. Slight overkill , but this should work well enough.
	for(Tools_StaticMessageIterator xIt(true); !xIt.Done(); xIt.Next())
	{
		if(xIt.GetCurrent() == pxMessage)
		{
			xIt.SetModified();
			if(xIt.IsCurrentInStaticMessageSystem())
			{
				//flag the owner of the message block as modified as well
				u_short usMessageBlock = xIt.GetCurrentMessageBlockID();

				Tools_Object* pxObject = Tools_Object_System::GetOwnerOfMessageBlock(usMessageBlock);
				if(pxObject && pxObject->IsEntityObject())
				{
					((Tools_Entity_Object*)pxObject)->SetEntityModified();
				}


			}
			break;
		}
	}
}

void Tools_MessageEditor::SetEditedMessageModified()
{
	if(s_pxMessageEditedByAttributeOverlay)
	{
		SetModified(s_pxMessageEditedByAttributeOverlay);
	}
}


////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

void Tools_MessageEditor::FixMessageBlockIfEmpty(u_short* pusBlockId)
{
	Asura_Assert(pusBlockId, "");
	if(!pusBlockId) return;

	if(*pusBlockId == ASURA_STATICMESSAGE_EMPTYBLOCK) return;

	if(!Asura_StaticMessage_System::IsMessageBlockValid(*pusBlockId))
	{
		//message block referred to doesn't exist
		Tools_Timed_Message_System::AddMessage (0xffff00, "Entity refers to empty message block %d. This has been removed.", *pusBlockId);
		SetModified(pusBlockId);
		
		*pusBlockId = ASURA_STATICMESSAGE_EMPTYBLOCK;
	}
	else if(!Asura_StaticMessage_System::GetNumberOfMessagesInBlock(*pusBlockId))
	{
		//message block is valid , but empty , so get rid of it.
		Asura_StaticMessage_System::DeleteMessageBlock(*pusBlockId);
		//SetModified(pusBlockId);
		
		*pusBlockId = ASURA_STATICMESSAGE_EMPTYBLOCK;
	}
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

void Tools_MessageEditor::SetReferenceObject(Tools_Object* pxObject)
{
	s_pxReferenceObject = pxObject;
	s_pxReferenceToolsObject = NULL;
	s_pxReferenceEntity = NULL;

	if(s_pxReferenceObject && s_pxReferenceObject->SupportsMessageEditor())
	{
		if(s_pxReferenceObject->IsEntityObject())
		{
			s_pxReferenceToolsObject = static_cast<Tools_Entity_Object*> (s_pxReferenceObject);
		}
		s_pxReferenceEntity = s_pxReferenceObject->GetServerEntity();
	}
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

void Tools_MessageEditor::OpenMessageEditorAtMessage(u_short usMessageID, int iMessageIndex)
{
	//make sure the sending object can be found
	Tools_Object* pxObject = Tools_Object_System::GetOwnerOfMessageBlock(usMessageID);
	if(!pxObject)
	{
		Tools_Timed_Message_System::AddMessage (0xffff00, "Failed to find sender of message.");
		return;
	}

	//open the message editor if needed
	if(!IsOpen()) CreateOverlay();

	//Select the sending object
	Tools_Object_System::GotoAndSelect(pxObject);
	SetReferenceObject(pxObject);


	//rebuld the tables for the selected object
	s_uSelectedTab = TAB_OUTGOING_MESSAGES;
	CloseMessageAttributeOverlay();

	RecreateTable();
 
 	for(u_int n = 0; n < NUM_TABS; n++)
	{
		s_paxMessageTables[n]->SetTableVisible(false);
	}

	s_paxMessageTables[s_uSelectedTab]->SetTableVisible(true);

 	s_bBanDelayReordering = false;

 	if(s_pxReferenceObject)
 	{
 		Asura_String xTitle = Asura_String("Message Editor - \"") + s_pxReferenceObject->GetName() + "\"";
 		s_pxOverlay->SetTitle(xTitle.GetString());
 	}
 	else
 	{
 		s_pxOverlay->SetTitle("Message Editor");
 	}
 	
	//search for the entry for the specified message(TBD)
	bool bFoundRow = false;
	
	Asura_List<Asura_MessageTableRow>& xRows = s_paxMessageTables[TAB_OUTGOING_MESSAGES]->GetRows();
	Asura_List_It<Asura_MessageTableRow> xIter(xRows);
	
	for(int iRowIndex =  0; !xIter.Done(); xIter.Next(), iRowIndex++)
	{
		Asura_MessageTableRow* pxRow = xIter.GetCurrent();

		
		
		if(pxRow->GetType() == Asura_MessageTableRow::TYPE_OUTGOING_MESSAGE)
		{
				
			Asura_MessageTableRow_Outgoing* pxOutgoingMessageRow = static_cast<Asura_MessageTableRow_Outgoing*> (pxRow);

			u_short* pusBlockId = pxOutgoingMessageRow->GetBlockId();
			if(pusBlockId && *pusBlockId == usMessageID)
			{
				if(pxOutgoingMessageRow->GetMessageIndex() == iMessageIndex)
				{
					pxOutgoingMessageRow->SetSelected(true);
					CreateMessageAttributesOverlay(pxOutgoingMessageRow->GetOutgoingMessagePointer());

					s_paxMessageTables[TAB_OUTGOING_MESSAGES]->ScrollToRow(iRowIndex);
					bFoundRow = true;
					break;
				}
			}
		}
	}
	if(!bFoundRow)
	{
		Tools_Timed_Message_System::AddMessage (0xffff00, "Failed to find message to be selected in the message editor.");
	}
}


#endif // ASURA_TOOLS

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

