//////////////////////////////////////////////////////////////////////////////
//
// Tools_Mode_AnimTemplates.cpp
//
// Created by NJD - 07 November 2006
//
// Copyright (C) 2006 Rebellion, All Rights Reserved.
//
//////////////////////////////////////////////////////////////////////////////

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

// core
#include "Core/Asura.h"

#ifdef ASURA_USE_AXON

// header
#include "AnimTemplates/Tools_Mode_AnimTemplates.h"

// asura
#include "Animation/Asura_Animation.h"
#include "Animation/Asura_Hierarchy.h"
#include "ChunkSystem/Axon_Chunk_AnimTemplates.h"
#include "FileAccess/Asura_FileAccess.h"
#include "Interface/Asura_Dialog.h"
#include "Interface/Asura_Menubar_System.h"
#include "Interface/Asura_Menubar.h"
#include "Interface/Asura_Overlay.h"
#include "Interface/Asura_Overlay_System.h"
#include "Interface/Asura_Table_Dialog.h"
#include "Mesh/Asura_Mesh.h"
#include "Mesh/Asura_Mesh_System.h"
#include "Rendering/Asura_Text_Output.h"

// axon
#include "AnimTemplates/Axon_Gamescene_AnimTemplate_System.h"
#include "AnimTemplates/Axon_Gamescene_AnimTemplates.h"

// tools
#include "Tools_Overlays.h"
#include "AnimTemplates/Tools_AnimTemplates_Play.h"
#include "Interface/Tools_ContextMenu_System.h"
#include "Hierarchy/Tools_Animation_Files.h"
#include "Load&Save/Tools_LoadedFileList.h"
#include "Load&Save/Tools_ModeDataFile.h"
#include "Load&Save/Tools_SaveAsura.h"
#include "Misc/Tools_Dialog.h"
#include "Misc/Tools_Modal_Overlay.h"
#include "Misc/Tools_Render_Options.h"
#include "Misc/Tools_Timed_Messages.h"
#include "Movement/Tools_Movement.h"
#include "Objects/Tools_Object_System.h"
#include "PC/FileSystem/Tools_FileSystem.h"

#ifdef ASURA_USE_GAMESCENE
#include "ChunkSystem/Asura_Chunk_Entity.h"
#include "ChunkSystem/Asura_Chunk_Stream.h"
#include "ChunkSystem/Asura_SaveBlock.h"
#include "Core/Asura_Graph.h"
#include "Entity/Asura_Entity_System.h"
#include "Entity/Asura_StaticMessage_System.h"
#include "Entity/BF3_Entity_Classification.h"
#include "Entity/BF3_Entity_Event.h"
#include "Entity/Locations/OrientedNode/GamesceneNode/AttractorNode/BF3_ServerNode_GamesceneAttractor.h"
#include "Entity/Locations/OrientedNode/GamesceneNode/AttractorNode/BF3_ServerNode_GS2Attractor.h"
#include "Entity/Locations/Spline/GamesceneSpline/BF3_ServerNode_GamesceneSpline.h"
#include "Entity/Locations/Spline/GamesceneSpline/BF3_ServerNode_GS2Spline.h"

#include "Behaviour/AxonBF3_Behaviour_GS2Node.h"
#include "Behaviour/BehaviourTypes/Axon_Behaviour_Types.h"
#include "Behaviour/BehaviourParamsTypes/Axon_BehaviourParams_GS2AttractorEnRoute.h"
#include "Behaviour/BehaviourParamsTypes/Axon_BehaviourParams_GS2Spline.h"
#include "Behaviour/BehaviourParamsTypes/AxonBF3_BehaviourParams_GS2Node.h"
#include "Core/Axon_Messages.h"

#include "Entity/Tools_Message_Editing.h"
#include "Load&Save/Tools_AsuraFileContents.h"
#include "Misc/Tools_MessageLog.h"
#include "Misc/Tools_ProgressIndicator.h"
#include "PC/FileSystem/Tools_FileSystem.h"

#include "ChunkSystem/BF3_Chunk_ConvertedGamescene.h"
#include "ChunkSystem/BF3_Chunk_Loaders.h"
#endif

//////////////////////////////////////////////////////////////////////////////
//
// Helper classes and functions.
//
//////////////////////////////////////////////////////////////////////////////

class Tools_CallbackOnClose_Overlay : public Asura_Overlay
{
public:
	Tools_CallbackOnClose_Overlay( const char *name, bool alwaysOnTop, Asura_Vector_2* position = 0, bool* minimised = 0 )
	: Asura_Overlay( name, alwaysOnTop, position, minimised )
	, m_pfnCallback( NULL )
	{
	}
	virtual ~Tools_CallbackOnClose_Overlay()
	{
		if ( m_bClosedByCloseGadget && m_pfnCallback ) { m_pfnCallback(); }
	}
	void SetOnClosingByCloseGadgetCallback( void ( *pfnCallback )() ) { m_pfnCallback = pfnCallback; }

private:
	void ( *m_pfnCallback )();
};

int CompareTemplateItems( const Asura_TreeDialog_Item& xNew, const Asura_TreeDialog_Item& xExisting )
{
	// Folder nodes always come before file (leaf) nodes.
	if ( xExisting.GetUserData() == uAXON_ANIMATION_SEQUENCE_ID_NONE ) { return 1; }

	return _stricmp( xNew.GetName(), xExisting.GetName() );
}

int CompareTemplateFolderItems( const Asura_TreeDialog_Item& xNew, const Asura_TreeDialog_Item& xExisting )
{
	// Folder nodes always come before file (leaf) nodes.
	if ( xExisting.GetUserData() != uAXON_ANIMATION_SEQUENCE_ID_NONE ) { return -1; }

	return _stricmp( xNew.GetName(), xExisting.GetName() );
}

int ComparePackageItems( const Asura_TreeDialog_Item& xNew, const Asura_TreeDialog_Item& xExisting )
{
	// Folder nodes always come before file (leaf) nodes.
	if ( xExisting.GetUserData() == Axon_GS2_AnimPackage::uUNSET_ID ) { return 1; }

	return _stricmp( xNew.GetName(), xExisting.GetName() );
}

int ComparePackageFolderItems( const Asura_TreeDialog_Item& xNew, const Asura_TreeDialog_Item& xExisting )
{
	// Folder nodes always come before file (leaf) nodes.
	if ( xExisting.GetUserData() != Axon_GS2_AnimPackage::uUNSET_ID ) { return -1; }

	return _stricmp( xNew.GetName(), xExisting.GetName() );
}

//////////////////////////////////////////////////////////////////////////////
//
// Tools_TableCell_Animation
//
//////////////////////////////////////////////////////////////////////////////

class Tools_TableCell_Animation : public Asura_TableCell_Dialog
{
	typedef Asura_TableCell_Dialog PARENT;

public:
	Tools_TableCell_Animation( Axon_GS2_AnimPackage* pxAnimPackage,
							   Asura_Table_Dialog& xTableDialog,
							   const Asura_Vector_2& xOffset, const Asura_Vector_2& xMinCellSize, const u_int uRow, const u_int uColumn )
	: PARENT( xTableDialog, xOffset, xMinCellSize, uRow, uColumn )
	, m_pxAnimPackage( pxAnimPackage )
	// The row will correspond to the mapping we are.
	, m_pxAnimMapping( static_cast< Axon_GS2_AnimMapping* >( pxAnimPackage->GetMapping( uRow ) ) )
	{
		Asura_Vector_2 xLocalOffset( 0.0f, 0.0f );

		Asura_String xAnimFilename;
		if ( m_pxAnimMapping->GetAnimHashID() == ASURA_HASH_ID_UNSET )
		{
			xAnimFilename = "not set";
		}
		else
		{
			xAnimFilename = Asura_HashedFilename_System::GetFilename( m_pxAnimMapping->GetAnimHashID() );
		}
		if ( xAnimFilename.IsBlank() )
		{
			xAnimFilename = "ANIM NOT FOUND";
		}
		Asura_Function_Dialog* pxFDialogue = new Asura_Function_Dialog( xAnimFilename.GetString(), xLocalOffset, PickAnim, reinterpret_cast< int >( this ) );
		AddDialogToPanel( pxFDialogue );
		xLocalOffset.y += Asura_Dialog::DefaultSpacing();
	}

	virtual int Compare( const Asura_TableCell_Dialog& xOther ) const
	{
		// We're only ever going to be sorting by column, so we can guarantee that the other cell is of the same type as us.
		const Tools_TableCell_Animation* pxOther = static_cast< const Tools_TableCell_Animation* >( &xOther );
		Asura_String xOne;
		if ( m_pxAnimMapping->GetAnimHashID() == ASURA_HASH_ID_UNSET )
		{
			xOne = "not set";
		}
		else
		{
			xOne = Asura_HashedFilename_System::GetFilename( m_pxAnimMapping->GetAnimHashID() );
		}
		Asura_String xTwo;
		if ( pxOther->m_pxAnimMapping->GetAnimHashID() == ASURA_HASH_ID_UNSET )
		{
			xTwo = "not set";
		}
		else
		{
			xTwo = Asura_HashedFilename_System::GetFilename( pxOther->m_pxAnimMapping->GetAnimHashID() );
		}
		return xOne.Compare( xTwo );
	}

private:
	static void PickAnim( int iThis )
	{
		Tools_TableCell_Animation* pxThis = reinterpret_cast< Tools_TableCell_Animation* >( iThis );

		Asura_String xAnimName;
		Asura_String xAnimFilename;
		Asura_Hierarchy_Anim* pxAnim = Tools_Animation_Files::ChooseAnimation( "Placeholder", NULL, &xAnimName, &xAnimFilename );
		if ( !pxAnim ) { return; }
		pxThis->m_pxAnimMapping->SetAnimHashIDs( Asura_GetHashID( xAnimName.GetString() ), Asura_GetHashID( xAnimFilename.GetString() ) );
		Tools_AnimTemplates::SetAnimPackageModified( pxThis->m_pxAnimPackage->GetUniqueID() );
		Tools_AnimTemplates::RequestRefreshAnimPackageOverlay();
	}

private:
	Axon_GS2_AnimPackage* m_pxAnimPackage;
	Axon_GS2_AnimMapping* m_pxAnimMapping;
};

class Tools_TableCell_ControlButtons : public Asura_TableCell_Dialog
{
	typedef Asura_TableCell_Dialog PARENT;

public:
	Tools_TableCell_ControlButtons( Axon_GS2_AnimPackage* pxAnimPackage,
									Asura_Table_Dialog& xTableDialog,
									const Asura_Vector_2& xOffset, const Asura_Vector_2& xMinCellSize, const u_int uRow, const u_int uColumn )
	: PARENT( xTableDialog, xOffset, xMinCellSize, uRow, uColumn )
	, m_pxAnimPackage( pxAnimPackage )
	{
		Asura_Vector_2 xLocalOffset( 1.0f, 0.0f );

		if ( GetRow() == 0 )
		{
			// Add the 'new' control only.
			Asura_Icon_Dialog* pxIconDlg = new Asura_Icon_Dialog( xLocalOffset, Tools_Dialog::GetIconTexture(), TOOLS_ICON_NEW );
			pxIconDlg->SetClickedFunction( NewAnimPackageEntry, reinterpret_cast< int >( this ) );
			AddDialogToPanel( pxIconDlg );

			return;
		}

		if ( GetRow() > 1 )
		{
			Asura_Icon_Dialog* pxIconDlg = new Asura_Icon_Dialog( xLocalOffset, Tools_Dialog::GetIconTexture(), TOOLS_ICON_ARROW_UP );
			pxIconDlg->SetClickedFunction( MoveAnimPackageEntryUp, reinterpret_cast< int >( this ) );
			AddDialogToPanel( pxIconDlg );
		}
		xLocalOffset.x += 2.0f;

		if ( GetRow() < static_cast< int >( m_pxAnimPackage->GetNumMappings() - 1 ) )
		{
			Asura_Icon_Dialog* pxIconDlg = new Asura_Icon_Dialog( xLocalOffset, Tools_Dialog::GetIconTexture(), TOOLS_ICON_ARROW_DOWN );
			pxIconDlg->SetClickedFunction( MoveAnimPackageEntryDown, reinterpret_cast< int >( this ) );
			AddDialogToPanel( pxIconDlg );
		}
		xLocalOffset.x += 2.0f;

		Asura_Icon_Dialog* pxIconDlg = new Asura_Icon_Dialog( xLocalOffset, Tools_Dialog::GetIconTexture(), TOOLS_ICON_DELETE );
		pxIconDlg->SetClickedFunction( DeleteAnimPackageEntry, reinterpret_cast< int >( this ) );
		AddDialogToPanel( pxIconDlg );
	}

	virtual int Compare( const Asura_TableCell_Dialog& xOther ) const
	{
		return 0;
	}

private:
	static void NewAnimPackageEntry( int iThis )
	{
		Tools_TableCell_ControlButtons* pxThis = reinterpret_cast< Tools_TableCell_ControlButtons* >( iThis );

		pxThis->m_pxAnimPackage->AddAnimMapping();

		Tools_AnimTemplates::SetAnimPackageModified( pxThis->m_pxAnimPackage->GetUniqueID() );
		Tools_AnimTemplates::RequestRefreshAnimPackageOverlay();
	}

	static void MoveAnimPackageEntryUp( int iThis )
	{
		Tools_TableCell_ControlButtons* pxThis = reinterpret_cast< Tools_TableCell_ControlButtons* >( iThis );

		Axon_GS2_AnimMapping* pxPasteBuffer;
		CutAnimPackageEntry( pxThis->m_pxAnimPackage, pxThis->GetRow(), pxPasteBuffer );
		PasteAnimPackageEntry( pxThis->m_pxAnimPackage, pxThis->GetRow() - 1, pxPasteBuffer );

		Tools_AnimTemplates::SetAnimPackageModified( pxThis->m_pxAnimPackage->GetUniqueID() );
		Tools_AnimTemplates::RequestRefreshAnimPackageOverlay();
	}

	static void MoveAnimPackageEntryDown( int iThis )
	{
		Tools_TableCell_ControlButtons* pxThis = reinterpret_cast< Tools_TableCell_ControlButtons* >( iThis );

		Axon_GS2_AnimMapping* pxPasteBuffer;
		CutAnimPackageEntry( pxThis->m_pxAnimPackage, pxThis->GetRow(), pxPasteBuffer );
		PasteAnimPackageEntry( pxThis->m_pxAnimPackage, pxThis->GetRow() + 1, pxPasteBuffer );

		Tools_AnimTemplates::SetAnimPackageModified( pxThis->m_pxAnimPackage->GetUniqueID() );
		Tools_AnimTemplates::RequestRefreshAnimPackageOverlay();
	}

	static void DeleteAnimPackageEntry( int iThis )
	{
		Tools_TableCell_ControlButtons* pxThis = reinterpret_cast< Tools_TableCell_ControlButtons* >( iThis );

		Axon_GS2_AnimMapping* pxToDelete;
		CutAnimPackageEntry( pxThis->m_pxAnimPackage, pxThis->GetRow(), pxToDelete );
		delete pxToDelete;

		Tools_AnimTemplates::SetAnimPackageModified( pxThis->m_pxAnimPackage->GetUniqueID() );
		Tools_AnimTemplates::RequestRefreshAnimPackageOverlay();
	}

	static void CutAnimPackageEntry( Axon_GS2_AnimPackage* pxAnimPackage, int iEntryIndex, Axon_GS2_AnimMapping*& pxPasteBuffer )
	{
		Asura_ListElt< Axon_GS2_AnimMapping* >* pxElt = pxAnimPackage->GetMappings()[ iEntryIndex ];
		pxPasteBuffer = pxElt->Data();
		pxAnimPackage->GetMappings().Remove( pxElt );
	}

	static void PasteAnimPackageEntry( Axon_GS2_AnimPackage* pxAnimPackage, int iEntryIndex, Axon_GS2_AnimMapping* pxPasteBuffer )
	{
		Asura_ListElt< Axon_GS2_AnimMapping* >* pxElt = pxAnimPackage->GetMappings()[ iEntryIndex - 1 ];
		pxAnimPackage->GetMappings().Insert_After( pxElt, pxPasteBuffer );
	}

private:
	Axon_GS2_AnimPackage* m_pxAnimPackage;
};

//////////////////////////////////////////////////////////////////////////////
//
// Tools_AnimTemplates
//
//////////////////////////////////////////////////////////////////////////////

#define szTEMPLATES_DIRECTORY	"AnimTemplates\\Templates\\"
#define szTEMPLATES_EXTENSION	".AnimTemplate"
#define szPACKAGES_DIRECTORY	"AnimTemplates\\Packages\\"
#define szPACKAGES_EXTENSION	".AnimPackage"
#define szFIDGET				"Fidget"
#define szLOOPING				"Looping"
#define szRIGID					"Rigid"
#define szSTATIC				"Static"
#define szFIDGET_DIRECTORY		szFIDGET "\\"
#define szLOOPING_DIRECTORY		szLOOPING "\\"
#define szRIGID_DIRECTORY		szRIGID "\\"
#define szSTATIC_DIRECTORY		szSTATIC "\\"

const char* Tools_AnimTemplates::s_aszTabNames[ NUM_TAB_NAMES ] =
{
	"Animation Templates",
	"Animation Packages"
};

Tools_AnimTemplates*	Tools_AnimTemplates::s_pxThis						= NULL;
bool					Tools_AnimTemplates::s_bFirstUpdate					= false;

bool					Tools_AnimTemplates::s_bRefreshMainOverlay			= false;
bool					Tools_AnimTemplates::s_bRefreshAnimTemplateOverlay	= false;
bool					Tools_AnimTemplates::s_bRefreshAnimPackageOverlay	= false;
u_int					Tools_AnimTemplates::s_uRefreshTemplate				= uAXON_ANIMATION_SEQUENCE_ID_NONE;
Asura_Hash_ID			Tools_AnimTemplates::s_uRefreshPackage				= Axon_GS2_AnimPackage::uUNSET_ID;

Asura_String			Tools_AnimTemplates::s_xPreviewDefaultSkin;
Asura_Guid				Tools_AnimTemplates::s_uPreviewObjectGuid			= ASURA_GUID_UNREGISTERED;
bool					Tools_AnimTemplates::s_bPreviewActive				= false;

bool					Tools_AnimTemplates::s_bMainOverlayOpenOnShutdown	= false;
bool					Tools_AnimTemplates::s_bMainOverlayWasOnTop			= false;
Asura_Vector_2			Tools_AnimTemplates::s_xMainOverlayPosition			= Asura_Vector_2( 3.0f, 3.0f );
u_int					Tools_AnimTemplates::s_uMainOverlayActiveTab		= TN_ANIM_TEMPLATES;
const Asura_Vector_2	Tools_AnimTemplates::s_xTreeDialogueDimensions( 40.0f, 20.0f );
Asura_TreeDialog_Tree	Tools_AnimTemplates::s_xAnimTemplateTree;
Asura_TreeDialog_Item*  Tools_AnimTemplates::s_pxSelectedTemplateTreeItemPrev	= NULL;
Asura_TreeDialog_Item*  Tools_AnimTemplates::s_pxSelectedTemplateTreeItem		= NULL;
Asura_String			Tools_AnimTemplates::s_xNewTemplateTypeString( "" );
Asura_TreeDialog_Tree	Tools_AnimTemplates::s_xAnimPackageTree;
Asura_TreeDialog_Item*  Tools_AnimTemplates::s_pxSelectedPackageTreeItemPrev = NULL;
Asura_TreeDialog_Item*  Tools_AnimTemplates::s_pxSelectedPackageTreeItem	 = NULL;

Asura_Vector_2			Tools_AnimTemplates::s_xAnimTemplateOverlayPosition = Asura_Vector_2( 6.0f, 6.0f );
u_int					Tools_AnimTemplates::s_uAnimTemplateActiveTabPrev	= 0;
u_int					Tools_AnimTemplates::s_uAnimTemplateActiveTab		= 0;
bool					Tools_AnimTemplates::s_bRefreshTemplateTreeSelection = false;
Asura_LinkList< Tools_AnimTemplates::AnimTemplateTabData > Tools_AnimTemplates::s_xOpenAnimTemplateList;
bool					Tools_AnimTemplates::s_bSelectedAnimTemplateChanged			= false;
bool					Tools_AnimTemplates::s_bSelectedAnimTemplateChangedRefresh	= false;
Asura_LinkList< u_int > Tools_AnimTemplates::s_xAnimTemplateModifiedList;

Asura_Vector_2					Tools_AnimTemplates::s_xAnimPackageOverlayPosition = Asura_Vector_2( 9.0f, 9.0f );
u_int							Tools_AnimTemplates::s_uAnimPackageActiveTabPrev = 0;
u_int							Tools_AnimTemplates::s_uAnimPackageActiveTab = 0;
bool							Tools_AnimTemplates::s_bRefreshPackageTreeSelection = false;
Asura_LinkList< u_int >			Tools_AnimTemplates::s_xOpenAnimPackageList;
bool							Tools_AnimTemplates::s_bSelectedAnimPackageChanged = false;
bool							Tools_AnimTemplates::s_bSelectedAnimPackageChangedRefresh = false;
Asura_LinkList< u_int >			Tools_AnimTemplates::s_xAnimPackageModifiedList;
Asura_String*					Tools_AnimTemplates::s_pxAnimPackageTableHeadings = NULL;

u_int							   Tools_AnimTemplates::s_uAnimTemplateCopyBufferType = NUM_AXON_ANIM_TEMPLATE_TYPES;
Axon_Gamescene_AnimTemplate_Entry* Tools_AnimTemplates::s_pxAnimTemplateCopyBufferEntry = NULL;

void Tools_AnimTemplates::LoadAnimTemplatesAndPackages()
{
	Tools_FileSystem::LoadAsuraFiles( szTEMPLATES_DIRECTORY szFIDGET_DIRECTORY "*" szTEMPLATES_EXTENSION, TOOLS_FILEFIND_RECURSIVE | TOOLS_FILEFIND_IGNOREBACKUPDIR );
	Tools_FileSystem::LoadAsuraFiles( szTEMPLATES_DIRECTORY szLOOPING_DIRECTORY "*" szTEMPLATES_EXTENSION, TOOLS_FILEFIND_RECURSIVE | TOOLS_FILEFIND_IGNOREBACKUPDIR );
	Tools_FileSystem::LoadAsuraFiles( szTEMPLATES_DIRECTORY szRIGID_DIRECTORY "*" szTEMPLATES_EXTENSION, TOOLS_FILEFIND_RECURSIVE | TOOLS_FILEFIND_IGNOREBACKUPDIR );
	Tools_FileSystem::LoadAsuraFiles( szTEMPLATES_DIRECTORY szSTATIC_DIRECTORY "*" szTEMPLATES_EXTENSION, TOOLS_FILEFIND_RECURSIVE | TOOLS_FILEFIND_IGNOREBACKUPDIR );
	Tools_FileSystem::LoadAsuraFiles( szPACKAGES_DIRECTORY "*" szPACKAGES_EXTENSION, TOOLS_FILEFIND_RECURSIVE | TOOLS_FILEFIND_IGNOREBACKUPDIR );

	EstablishLocalWritableAttributes();
}

void Tools_AnimTemplates::Initialise()
{
	s_pxThis = Project_CreateAnimTemplateTools();
	s_pxThis->InitialiseMode();
}

void Tools_AnimTemplates::Update()
{
	s_pxThis->UpdateMode();
}

void Tools_AnimTemplates::Render()
{
	s_pxThis->RenderMode();
}

void Tools_AnimTemplates::Shutdown()
{
	s_pxThis->ShutdownMode();
	delete s_pxThis;
	s_pxThis = NULL;
}

void Tools_AnimTemplates::SetFilesToCheckForExternalModification()
{
	for ( Axon_GS2_AnimTemplate_Iterator xTemplateIter; !xTemplateIter.Done(); xTemplateIter.Next() )
	{
		const Axon_Gamescene_AnimTemplate* pxTemplate = xTemplateIter.GetCurrent();

		const Asura_String xFullName( pxTemplate->GetPath() + "\\" + pxTemplate->GetName() + szTEMPLATES_EXTENSION );
		Tools_LoadedFileList::TrackExternalModification( xFullName.GetString() );
	}
}

void Tools_AnimTemplates::SaveAll()
{
	for ( Asura_LinkList_It< u_int > xModifiedTemplIt( s_xAnimTemplateModifiedList ); !xModifiedTemplIt.Done();   )
	{
		const Axon_Gamescene_AnimTemplate* pxTemplate = Axon_Gamescene_AnimTemplate_System::GetAnimTemplateByID( *xModifiedTemplIt.GetCurrent() );
		if ( !pxTemplate )
		{
			xModifiedTemplIt.DeleteCurrent();
			continue;
		}

		bool bSaved = false;

		const Asura_String xFullName( pxTemplate->GetPath() + "\\" + pxTemplate->GetName() + szTEMPLATES_EXTENSION );
		if ( Tools_SaveAsura::CanSaveFile( xFullName.GetString(), true ) )
		{
			Tools_FileSystem::EnsureDirectoryExists( xFullName.GetString() );
			if ( Axon_Chunk_AnimTemplates::SaveAnimTemplateData( xFullName.GetString(), *pxTemplate ) )
			{
				Tools_LoadedFileList::UpdateModifiedTime( xFullName.GetString() );
				bSaved = true;					
			}
			else
			{
				Tools_Timed_Message_System::AddMessage( Tools_Timed_Message_System::COLOUR_ERROR,
					"Failed to save %s - is the file checked out?", xFullName.GetString() );
			}
		}

		if ( bSaved )
		{
			xModifiedTemplIt.DeleteCurrent();
		}
		else
		{
			xModifiedTemplIt.Next();
		}
	}

	ResetAnimTemplateTreeColours();
	SetAnimTemplateTreeModifiedColours();

	for ( Asura_LinkList_It< u_int > xModifiedPackIt( s_xAnimPackageModifiedList ); !xModifiedPackIt.Done();   )
	{
		const Axon_GS2_AnimPackage* pxPackage = Axon_Gamescene_AnimTemplate_System::GetAnimPackageByID( *xModifiedPackIt.GetCurrent() );
		if ( !pxPackage )
		{
			xModifiedPackIt.DeleteCurrent();
			continue;
		}

		bool bSaved = false;

		const Asura_String xFullName( pxPackage->GetPath() + "\\" + pxPackage->GetName() + szPACKAGES_EXTENSION );
		if ( Tools_SaveAsura::CanSaveFile( xFullName.GetString(), true ) )
		{
			Tools_FileSystem::EnsureDirectoryExists( xFullName.GetString() );
			if ( Axon_Chunk_AnimTemplates::SaveAnimPackageData( xFullName.GetString(), *pxPackage ) )
			{
				Tools_LoadedFileList::UpdateModifiedTime( xFullName.GetString() );
				bSaved = true;					
			}
			else
			{
				Tools_Timed_Message_System::AddMessage( Tools_Timed_Message_System::COLOUR_ERROR,
					"Failed to save %s - is the file checked out?", xFullName.GetString() );
			}
		}

		if ( bSaved )
		{
			xModifiedPackIt.DeleteCurrent();
		}
		else
		{
			xModifiedPackIt.Next();
		}
	}

	ResetAnimPackageTreeColours();
	SetAnimPackageTreeModifiedColours();

	if ( s_xAnimTemplateModifiedList.Size() == 0 && s_xAnimPackageModifiedList.Size() == 0 )
	{
		Tools_SaveAsura::SetAnimTemplatesModified( false );
		Tools_Timed_Message_System::AddMessage( Tools_Timed_Message_System::COLOUR_SUCCESS, "Saved Anim Templates successfully." );
	}
}

bool Tools_AnimTemplates::MainOverlayIsOpen()
{
	return ( s_pxThis ? Asura_Overlay_System::GetOverlay( GetMainOverlayID() ) != NULL : false );
}

void Tools_AnimTemplates::ToggleOverlays()
{
	if ( !s_pxThis ) { return; }

	if ( MainOverlayIsOpen() )
	{
		s_pxThis->DestroyMainOverlay();
		s_pxThis->DestroyAnimTemplateOverlay();
		s_pxThis->DestroyAnimPackageOverlay();
	}
	else
	{
		s_pxThis->CreateMainOverlay();
		if ( s_xOpenAnimTemplateList.Size() )
		{
			s_pxThis->CreateAnimTemplateOverlay();
		}
		if ( s_xOpenAnimPackageList.Size() )
		{
			s_pxThis->CreateAnimPackageOverlay();
		}
	}
}

void Tools_AnimTemplates::OpenAnimTemplate( const u_int uUniqueID )
{
	if ( !s_pxThis ) { return; }

	if ( !MainOverlayIsOpen() )
	{
		ToggleOverlays();
	}

	EditAnimTemplate( uUniqueID );
}

bool Tools_AnimTemplates::ChooseAnimTemplate( u_int& uSelectedUniqueID,
											  u_int uTypeBitfield/* = 0xffffffff*/, u_int uUniqueIDToIgnore/*= uAXON_ANIMATION_SEQUENCE_ID_NONE*/,
											  u_int uInitiallySelectedID/* = uAXON_ANIMATION_SEQUENCE_ID_NONE*/, bool bAllowNone/* = true*/ )
{
	bool bAddedOne = false;

	Asura_TreeDialog_Tree xTemplateTree;
	for ( u_int u = 0; u < Axon_Gamescene_AnimTemplate_System::GetNumAnimTemplates(); ++u )
	{
		const Axon_Gamescene_AnimTemplate* pxTemplate = Axon_Gamescene_AnimTemplate_System::GetAnimTemplateByIndex( u );

		if ( pxTemplate && (uTypeBitfield & (1 << pxTemplate->m_uTemplateType)) && pxTemplate->m_uUniqueID != uUniqueIDToIgnore )
		{
			Asura_String xFullFileName;
			xFullFileName = pxTemplate->GetPath() + "\\";
			xFullFileName.Replace( szTEMPLATES_DIRECTORY, "" );
			xFullFileName += pxTemplate->GetName();
			Asura_TreeDialog_Item* pxItem = xTemplateTree.AddFilePath( xFullFileName.GetString(), pxTemplate->GetUniqueID(), uAXON_ANIMATION_SEQUENCE_ID_NONE );

			const bool bInitiallySelected = ( uInitiallySelectedID == uAXON_ANIMATION_SEQUENCE_ID_NONE && !bAllowNone ) ?
				( u == 0 ) :
				( pxTemplate->m_uUniqueID == uInitiallySelectedID );
			if ( bInitiallySelected )
			{
				xTemplateTree.SetSelectedItem( pxItem );
			}

			bAddedOne = true;
		}
	}
	if ( bAllowNone )
	{
		Asura_TreeDialog_Item* pxItem = xTemplateTree.AddItem( "*NONE*", uAXON_ANIMATION_SEQUENCE_ID_NONE );
		if ( uInitiallySelectedID == uAXON_ANIMATION_SEQUENCE_ID_NONE )
		{
			xTemplateTree.SetSelectedItem( pxItem );
		}
	}

	if ( !bAddedOne )
	{
		if ( Axon_Gamescene_AnimTemplate_System::GetNumAnimTemplates() )
		{
			Tools_Timed_Message_System::AddMessage( Tools_Timed_Message_System::COLOUR_ERROR, "No templates of the correct type are available." );
		}
		else
		{
			Tools_Timed_Message_System::AddMessage( Tools_Timed_Message_System::COLOUR_ERROR, "No templates exist." );
		}
		return false;
	}
	TreeSelect_Overlay xOverlay( "Choose An Anim Template", &xTemplateTree, false );
	if ( !xOverlay.DoModal() )
	{
		return false;
	}
	uSelectedUniqueID = xOverlay.GetSelectionData();
	return true;
}

bool Tools_AnimTemplates::ChooseAnimPackage( u_int& uSelectedUniqueID,
											 u_int uUniqueIDToIgnore /*= Axon_GS2_AnimPackage::uUNSET_ID*/,
											 u_int uInitiallySelectedID /*= Axon_GS2_AnimPackage::uUNSET_ID*/,
											 bool bAllowNone /*= true*/ )
{
	bool bAddedOne = false;

	Asura_TreeDialog_Tree xPackageTree;
	for ( u_int u = 0; u < Axon_Gamescene_AnimTemplate_System::GetNumAnimPackages(); ++u )
	{
		const Axon_GS2_AnimPackage* pxPackage = Axon_Gamescene_AnimTemplate_System::GetAnimPackageByIndex( u );

		if ( pxPackage && pxPackage->m_uUniqueID != uUniqueIDToIgnore )
		{
			Asura_String xFullFileName;
			xFullFileName = pxPackage->GetPath() + "\\";
			xFullFileName.Replace( szTEMPLATES_DIRECTORY, "" );
			xFullFileName += pxPackage->GetName();
			Asura_TreeDialog_Item* pxItem = xPackageTree.AddFilePath( xFullFileName.GetString(), pxPackage->GetUniqueID(), Axon_GS2_AnimPackage::uUNSET_ID );

			const bool bInitiallySelected = ( uInitiallySelectedID == Axon_GS2_AnimPackage::uUNSET_ID && !bAllowNone ) ?
				( u == 0 ) :
				( pxPackage->m_uUniqueID == uInitiallySelectedID );
			if ( bInitiallySelected )
			{
				xPackageTree.SetSelectedItem( pxItem );
			}

			bAddedOne = true;
		}
	}
	if ( bAllowNone )
	{
		Asura_TreeDialog_Item* pxItem = xPackageTree.AddItem( "*NONE*", Axon_GS2_AnimPackage::uUNSET_ID );
		if ( uInitiallySelectedID == Axon_GS2_AnimPackage::uUNSET_ID )
		{
			xPackageTree.SetSelectedItem( pxItem );
		}
	}

	if ( !bAddedOne )
	{
		Tools_Timed_Message_System::AddMessage( Tools_Timed_Message_System::COLOUR_ERROR, "No packages exist." );
		return false;
	}
	TreeSelect_Overlay xOverlay( "Choose An Anim Package", &xPackageTree, false );
	if ( !xOverlay.DoModal() )
	{
		return false;
	}
	uSelectedUniqueID = xOverlay.GetSelectionData();
	return true;
}

bool Tools_AnimTemplates::SetAnimPackageModified( const u_int uUniqueID )
{
	if ( s_xAnimPackageModifiedList.Find( uUniqueID ) )
	{
		return false;
	}
	else
	{
		s_xAnimPackageModifiedList.Push_Back( uUniqueID );
		SetAnimPackageTreeModifiedColours();

		Tools_SaveAsura::SetAnimTemplatesModified();

		return true;
	}
}

bool Tools_AnimTemplates::SetAnimTemplateModified( const u_int uUniqueID )
{
	if ( s_xAnimTemplateModifiedList.Find( uUniqueID ) )
	{
		return false;
	}
	else
	{
		s_xAnimTemplateModifiedList.Push_Back( uUniqueID );
		SetAnimTemplateTreeModifiedColours();

		Tools_SaveAsura::SetAnimTemplatesModified();

		return true;
	}
}

void Tools_AnimTemplates::InitialiseMode()
{
	s_bFirstUpdate = true;

	TransferModeData( TOOLSMODEDATAFILE_LOAD );

	if ( s_bMainOverlayOpenOnShutdown )
	{
		CreateMainOverlay();
	}
	if ( s_xOpenAnimTemplateList.Size() )
	{
		s_pxThis->CreateAnimTemplateOverlay();
	}
	if ( s_xOpenAnimPackageList.Size() )
	{
		s_pxThis->CreateAnimPackageOverlay();
	}

	const u_int uNumHeadings = GetNumAnimPackageMappingFactors() + 2;
	s_pxAnimPackageTableHeadings = new Asura_String[ uNumHeadings ];
	for ( u_int u = 0; u < uNumHeadings - 2; ++u )
	{
		s_pxAnimPackageTableHeadings[ u ] = GetAnimPackageMappingNames()[ u ];
	}
	s_pxAnimPackageTableHeadings[ uNumHeadings - 2 ] = "Anim";
	s_pxAnimPackageTableHeadings[ uNumHeadings - 1 ] = "";

	Tools_AnimTemplates_Play::Initialise();
}

void Tools_AnimTemplates::UpdateMode()
{
	// If the main overlay has changed rank then refresh the tree colours.
	const bool bMainOverlayIsOnTop = ( Asura_Overlay_System::GetTopOverlayID() == GetMainOverlayID() );
	if ( bMainOverlayIsOnTop != s_bMainOverlayWasOnTop || s_bFirstUpdate )
	{
		SetAnimTemplateTreeModifiedColours();
	}
	s_bMainOverlayWasOnTop = bMainOverlayIsOnTop;

	// If a template/package refresh has been requested then action this.
	if ( s_bSelectedAnimTemplateChangedRefresh )
	{
		s_bSelectedAnimTemplateChangedRefresh = false;
		s_bSelectedAnimTemplateChanged = true;
		s_bRefreshAnimTemplateOverlay = true;
	}
	if ( s_bSelectedAnimTemplateChanged )
	{
		s_bSelectedAnimTemplateChanged = false;

		Axon_Gamescene_AnimTemplate* pxTemplate = GetSelectedAnimTemplate();
		Asura_Assert( pxTemplate, "Tools_AnimTemplates::UpdateMode - something's changed and we can't work out what" );
		if ( pxTemplate )
		{
			if ( pxTemplate->GetType() == AXON_ATT_FIDGETBEHAVIOUR )
			{
				static_cast< Axon_Gamescene_FidgetAnimTemplate* >( pxTemplate )->CalculateTotalWeighting();
			}

			SetAnimTemplateModified( pxTemplate->GetUniqueID() );
		}
	}
	if ( s_bSelectedAnimPackageChangedRefresh )
	{
		s_bSelectedAnimPackageChangedRefresh = false;
		s_bSelectedAnimPackageChanged = true;
		s_bRefreshAnimPackageOverlay = true;
	}
	if ( s_bSelectedAnimPackageChanged )
	{
		s_bSelectedAnimPackageChanged = false;

		const Axon_GS2_AnimPackage* pxPackage = GetSelectedAnimPackage();
		Asura_Assert( pxPackage, "Tools_AnimTemplates::UpdateMode - something's changed and we can't work out what" );
		if ( pxPackage )
		{
			SetAnimPackageModified( pxPackage->GetUniqueID() );
		}
	}

	if ( s_bRefreshMainOverlay )
	{
		s_bRefreshMainOverlay = false;
		s_pxThis->DestroyMainOverlay();
		s_pxThis->CreateMainOverlay();
	}
	if ( s_bRefreshAnimTemplateOverlay )
	{
		s_bRefreshAnimTemplateOverlay = false;
		s_pxThis->DestroyAnimTemplateOverlay();
		s_pxThis->CreateAnimTemplateOverlay();

		if ( s_uRefreshTemplate != uAXON_ANIMATION_SEQUENCE_ID_NONE )
		{
			const Axon_Gamescene_AnimTemplate* pxTemplate = Axon_Gamescene_AnimTemplate_System::GetAnimTemplateByID( s_uRefreshTemplate );
			if ( pxTemplate )
			{
				Asura_Overlay* pxOverlay = Asura_Overlay_System::GetOverlay( GetAnimTemplateOverlayID() );
				if ( pxOverlay )
				{
					Asura_Tabs_Dialog* pxTabsDialogue = static_cast< Asura_Tabs_Dialog* >( pxOverlay->GetDialog( GetAnimTemplateOverlayTabDialogueID() ) );
					if ( pxTabsDialogue )
					{
						pxTabsDialogue->SelectTabByName( pxTemplate->GetName() );
					}
				}
			}
		}
		Asura_Overlay_System::BringOverlayToTop( GetAnimTemplateOverlayID() );
	}
	if ( s_bRefreshAnimPackageOverlay )
	{
		s_bRefreshAnimPackageOverlay = false;
		s_pxThis->DestroyAnimPackageOverlay();
		s_pxThis->CreateAnimPackageOverlay();
		Asura_Overlay_System::BringOverlayToTop( GetAnimPackageOverlayID() );

		if ( s_uRefreshPackage != Axon_GS2_AnimPackage::uUNSET_ID )
		{
			const Axon_GS2_AnimPackage* pxPackage = Axon_Gamescene_AnimTemplate_System::GetAnimPackageByID( s_uRefreshPackage );
			if ( pxPackage )
			{
				Asura_Overlay* pxOverlay = Asura_Overlay_System::GetOverlay( GetAnimPackageOverlayID() );
				if ( pxOverlay )
				{
					Asura_Tabs_Dialog* pxTabsDialogue = static_cast< Asura_Tabs_Dialog* >( pxOverlay->GetDialog( GetAnimPackageOverlayTabDialogueID() ) );
					if ( pxTabsDialogue )
					{
						pxTabsDialogue->SelectTabByName( pxPackage->GetName() );
					}
				}
			}
		}
	}

	if ( s_uAnimTemplateActiveTab != s_uAnimTemplateActiveTabPrev || s_bRefreshTemplateTreeSelection )
	{
		s_uAnimTemplateActiveTabPrev = s_uAnimTemplateActiveTab;
		s_bRefreshTemplateTreeSelection = false;

		// Update the selection in the main template tree.
		Asura_Overlay* pxOverlay = Asura_Overlay_System::GetOverlay( GetAnimTemplateOverlayID() );
		if ( pxOverlay )
		{
			Asura_Tabs_Dialog* pxTabsDialogue = static_cast< Asura_Tabs_Dialog* >( pxOverlay->GetDialog( GetAnimTemplateOverlayTabDialogueID() ) );
			if ( pxTabsDialogue )
			{
				const Axon_Gamescene_AnimTemplate* pxTemplate = Axon_Gamescene_AnimTemplate_System::GetAnimTemplateByName( pxTabsDialogue->GetSelectedTabName() );
				if ( pxTemplate )
				{
					Asura_TreeDialog_Item* pxItem = s_xAnimTemplateTree.FindByData( pxTemplate->GetUniqueID() );
					if ( pxItem )
					{
						s_xAnimTemplateTree.SetSelectedItem( pxItem );
					}
				}
			}
		}
	}

	s_pxSelectedTemplateTreeItem = s_xAnimTemplateTree.GetSelectedItem();
	if ( s_pxSelectedTemplateTreeItem != s_pxSelectedTemplateTreeItemPrev )
	{
		s_pxSelectedTemplateTreeItemPrev = s_pxSelectedTemplateTreeItem;

		if ( s_pxSelectedTemplateTreeItem )
		{
			if ( s_pxSelectedTemplateTreeItem->GetUserData() != uAXON_ANIMATION_SEQUENCE_ID_NONE )
			{
				// Update the selection in the anim templates overlay.
				Asura_Overlay* pxOverlay = Asura_Overlay_System::GetOverlay( GetAnimTemplateOverlayID() );
				if ( pxOverlay )
				{
					Asura_Tabs_Dialog* pxTabsDialogue = static_cast< Asura_Tabs_Dialog* >( pxOverlay->GetDialog( GetAnimTemplateOverlayTabDialogueID() ) );
					if ( pxTabsDialogue )
					{
						pxTabsDialogue->SelectTabByName( s_pxSelectedTemplateTreeItem->GetName(), false );
					}
				}
			}

			// Update the string for the 'new' dialogue.
			if ( FidgetTemplateTreeItemIsSelected() )
			{
				s_xNewTemplateTypeString = szFIDGET;
			}
			else if ( LoopingTemplateTreeItemIsSelected() )
			{
				s_xNewTemplateTypeString = szLOOPING;
			}
			else if ( RigidTemplateTreeItemIsSelected() )
			{
				s_xNewTemplateTypeString = szRIGID;
			}
			else if ( StaticTemplateTreeItemIsSelected() )
			{
				s_xNewTemplateTypeString = szSTATIC;
			}
			else
			{
				s_xNewTemplateTypeString = "";
			}
		}
	}

	if ( s_uAnimPackageActiveTab != s_uAnimPackageActiveTabPrev || s_bRefreshPackageTreeSelection )
	{
		s_uAnimPackageActiveTabPrev = s_uAnimPackageActiveTab;
		s_bRefreshPackageTreeSelection = false;

		// Update the selection in the main package tree.
		Asura_Overlay* pxOverlay = Asura_Overlay_System::GetOverlay( GetAnimPackageOverlayID() );
		if ( pxOverlay )
		{
			Asura_Tabs_Dialog* pxTabsDialogue = static_cast< Asura_Tabs_Dialog* >( pxOverlay->GetDialog( GetAnimPackageOverlayTabDialogueID() ) );
			if ( pxTabsDialogue )
			{
				const Axon_GS2_AnimPackage* pxPackage = Axon_Gamescene_AnimTemplate_System::GetAnimPackageByName( pxTabsDialogue->GetSelectedTabName() );
				if ( pxPackage )
				{
					Asura_TreeDialog_Item* pxItem = s_xAnimPackageTree.FindByData( pxPackage->GetUniqueID() );
					if ( pxItem )
					{
						s_xAnimPackageTree.SetSelectedItem( pxItem );
					}
				}
			}
		}
	}

	s_pxSelectedPackageTreeItem = s_xAnimPackageTree.GetSelectedItem();
	if ( s_pxSelectedPackageTreeItem != s_pxSelectedPackageTreeItemPrev )
	{
		s_pxSelectedPackageTreeItemPrev = s_pxSelectedPackageTreeItem;

		if ( s_pxSelectedPackageTreeItem )
		{
			if ( s_pxSelectedPackageTreeItem->GetUserData() != Axon_GS2_AnimPackage::uUNSET_ID )
			{
				// Update the selection in the anim package overlay.
				Asura_Overlay* pxOverlay = Asura_Overlay_System::GetOverlay( GetAnimPackageOverlayID() );
				if ( pxOverlay )
				{
					Asura_Tabs_Dialog* pxTabsDialogue = static_cast< Asura_Tabs_Dialog* >( pxOverlay->GetDialog( GetAnimPackageOverlayTabDialogueID() ) );
					if ( pxTabsDialogue )
					{
						pxTabsDialogue->SelectTabByName( s_pxSelectedPackageTreeItem->GetName(), false );
					}
				}
			}
		}
	}

	s_uRefreshTemplate = uAXON_ANIMATION_SEQUENCE_ID_NONE;
	s_uRefreshPackage = Axon_GS2_AnimPackage::uUNSET_ID;
	s_bFirstUpdate = false;
}

void Tools_AnimTemplates::RenderMode()
{
	Tools_AnimTemplates_Play::Render();
}

void Tools_AnimTemplates::ShutdownMode()
{
	if ( s_bPreviewActive )
	{
		StopPreview();
	}
	Tools_AnimTemplates_Play::Shutdown();

	delete [] s_pxAnimPackageTableHeadings;
	s_pxAnimPackageTableHeadings = NULL;

    s_bMainOverlayOpenOnShutdown = MainOverlayIsOpen();
	if ( s_bMainOverlayOpenOnShutdown )
	{
		ToggleOverlays();
	}

	ClearTemplateCopyBuffer();

	TransferModeData( TOOLSMODEDATAFILE_SAVE );
}

void Tools_AnimTemplates::TransferModeData( const int iMode )
{
	Tools_ModeDataFile xFile( "AnimationTemplates.dat", iMode );
	TransferModeData( xFile );
}

void Tools_AnimTemplates::TransferModeData( Tools_ModeDataFile& xFile )
{
	u_int uVersion = uCURRENT_MODE_FILE_VERSION;

	xFile.Transfer( uVersion );

	if ( uVersion == uCURRENT_MODE_FILE_VERSION )
	{
		xFile.Transfer( s_xMainOverlayPosition );
		xFile.Transfer( s_xAnimTemplateOverlayPosition );
		xFile.Transfer( s_xAnimPackageOverlayPosition );
		xFile.Transfer( s_xPreviewDefaultSkin );
		xFile.Transfer( s_uPreviewObjectGuid );
	}
}

void Tools_AnimTemplates::CreateMainOverlay()
{
	if ( MainOverlayIsOpen() ) { return; }

	Asura_Overlay* pxNewOverlay;
	pxNewOverlay = new Asura_Overlay( "Animation Templates", false, &s_xMainOverlayPosition );
	pxNewOverlay->m_uID = GetMainOverlayID();

	Asura_Vector_2 xOffset = Asura_Vector_2( 1.0f, 3.0f );
	s_pxThis->AddMainOverlayItems( *pxNewOverlay, xOffset );
	pxNewOverlay->SetContextMenuCallback( ContextMenuCallback, NULL );

	Asura_Overlay_System::SubmitNewOverlay( pxNewOverlay );
}

void Tools_AnimTemplates::AddMainOverlayItems( Asura_Overlay& xNewOverlay, Asura_Vector_2& xOffset )
{
	Asura_Tabs_Dialog* pxTabsDialogue = new Asura_Tabs_Dialog( xOffset, s_aszTabNames, sizeof( s_aszTabNames ) / sizeof( s_aszTabNames[ 0 ] ) );
	pxTabsDialogue->SetControlID( GetMainOverlayTabDialogueID() );
	xNewOverlay.AddDialog( pxTabsDialogue );

	pxTabsDialogue->SelectTab( TN_ANIM_TEMPLATES );
	xOffset.y = pxTabsDialogue->m_xMaxCoord.y + 0.5f;
	float fTabsEndY = xOffset.y;

	// Anim template tree.
	CreateAnimTemplateTree();
	SetAnimTemplateTreeModifiedColours();
	Asura_Tree_Dialog* pxNewTreeDialogue = new Asura_Tree_Dialog( xOffset, &s_xAnimTemplateTree, s_xTreeDialogueDimensions.x, s_xTreeDialogueDimensions.y );
	pxNewTreeDialogue->SetControlID( GetMainOverlayTemplateTreeDialogueID() );
	pxNewTreeDialogue->SetDoubleClickedFunction( AnimTemplateTreeDoubleClicked );
	pxTabsDialogue->AddDialog( pxNewTreeDialogue );
	xOffset.y += s_xTreeDialogueDimensions.y + 1.0f;

	// Anim template editing dialogues.
	Asura_Dialog* pxNewDialogue = new Asura_Dialog( "Template:", xOffset );
	pxTabsDialogue->AddDialog( pxNewDialogue );
	xOffset.y += Asura_Dialog::DefaultSpacing();

	pxNewDialogue = new Asura_Function_Dialog( "New", xOffset, NewAnimTemplate, NULL, AnyTemplateTreeItemIsSelected );
	pxTabsDialogue->AddDialog( pxNewDialogue );
	xOffset.x += 5.0f;

	pxNewDialogue = new Asura_Dynamic_Dialog( &s_xNewTemplateTypeString, xOffset );
	pxTabsDialogue->AddDialog( pxNewDialogue );
	xOffset.x += 6.0f;

	pxNewDialogue = new Asura_Function_Dialog( "Edit", xOffset, EditAnimTemplate, NULL, AnimTemplateTreeTemplateItemIsSelected );
	pxTabsDialogue->AddDialog( pxNewDialogue );
	xOffset.x += 11.0f;

	pxNewDialogue = new Asura_Function_Dialog( "Delete", xOffset, DeleteAnimTemplate, NULL, LocalWritableTemplateTreeItemIsSelected );
	pxTabsDialogue->AddDialog( pxNewDialogue );
	xOffset.x -= 22.0f;
	xOffset.y += Asura_Dialog::LargeSpacing();

	pxNewDialogue = new Asura_Dialog( "Folder:", xOffset );
	pxTabsDialogue->AddDialog( pxNewDialogue );
	xOffset.y += Asura_Dialog::DefaultSpacing();

	pxNewDialogue = new Asura_Function_Dialog( "New", xOffset, NewAnimTemplateFolder, NULL, AnyTemplateTreeItemIsSelected );
	pxTabsDialogue->AddDialog( pxNewDialogue );
	xOffset.x += 11.0f;

	pxNewDialogue = new Asura_Function_Dialog( "Delete", xOffset, DeleteAnimTemplateFolder, NULL, EmptyFolderTemplateTreeItemIsSelected );
	pxTabsDialogue->AddDialog( pxNewDialogue );
	xOffset.x -= 11.0f;
	xOffset.y += Asura_Dialog::LargeSpacing();
	fTabsEndY = Asura_Maths::Max( fTabsEndY, xOffset.y );

	if ( GetNumAnimPackageMappingFactors() > 0 )
	{
		pxTabsDialogue->SelectTab( TN_ANIM_PACKAGES );
		xOffset.y = pxTabsDialogue->m_xMaxCoord.y + 0.5f;

		// Anim package tree.
		CreateAnimPackageTree();
		pxNewTreeDialogue = new Asura_Tree_Dialog( xOffset, &s_xAnimPackageTree, s_xTreeDialogueDimensions.x, s_xTreeDialogueDimensions.y );
		pxNewTreeDialogue->SetDoubleClickedFunction( AnimPackageTreeDoubleClicked );
		pxTabsDialogue->AddDialog( pxNewTreeDialogue );
		xOffset.y += s_xTreeDialogueDimensions.y + 1.0f;

		// Anim package editing dialogues.
		pxNewDialogue = new Asura_Dialog( "Package:", xOffset );
		pxTabsDialogue->AddDialog( pxNewDialogue );
		xOffset.y += Asura_Dialog::DefaultSpacing();

		pxNewDialogue = new Asura_Function_Dialog( "New", xOffset, NewAnimPackage, NULL, AnyPackageTreeItemIsSelected );
		pxTabsDialogue->AddDialog( pxNewDialogue );
		xOffset.x += 11.0f;

		pxNewDialogue = new Asura_Function_Dialog( "Edit", xOffset, EditAnimPackage, NULL, AnimPackageTreeItemIsSelected );
		pxTabsDialogue->AddDialog( pxNewDialogue );
		xOffset.x += 11.0f;

		pxNewDialogue = new Asura_Function_Dialog( "Delete", xOffset, DeleteAnimPackage, NULL, LocalWritablePackageTreeItemIsSelected );
		pxTabsDialogue->AddDialog( pxNewDialogue );
		xOffset.x -= 22.0f;
		xOffset.y += Asura_Dialog::LargeSpacing();

		pxNewDialogue = new Asura_Dialog( "Folder:", xOffset );
		pxTabsDialogue->AddDialog( pxNewDialogue );
		xOffset.y += Asura_Dialog::DefaultSpacing();

		pxNewDialogue = new Asura_Function_Dialog( "New", xOffset, NewAnimPackageFolder );
		pxTabsDialogue->AddDialog( pxNewDialogue );
		xOffset.x += 11.0f;

		pxNewDialogue = new Asura_Function_Dialog( "Delete", xOffset, DeleteAnimPackageFolder, NULL, EmptyFolderPackageTreeItemIsSelected );
		pxTabsDialogue->AddDialog( pxNewDialogue );
		xOffset.x -= 11.0f;
		xOffset.y += Asura_Dialog::LargeSpacing();
		fTabsEndY = Asura_Maths::Max( fTabsEndY, xOffset.y );
	}

	xOffset.y = fTabsEndY;

	pxNewDialogue = new Asura_Dialog( "Preview:", xOffset );
	xNewOverlay.AddDialog( pxNewDialogue );
	xOffset.y += Asura_Dialog::DefaultSpacing();

	Asura_String xPreviewDialogueText( "Default Skin: " );
	if ( s_xPreviewDefaultSkin.IsBlank() )
	{
		xPreviewDialogueText += "unset";
	}
	else
	{
		xPreviewDialogueText += s_xPreviewDefaultSkin;
	}
	pxNewDialogue = new Asura_Function_Dialog( xPreviewDialogueText.GetString(), xOffset, SetPreviewDefaultSkin );
	xNewOverlay.AddDialog( pxNewDialogue );
	xOffset.y += Asura_Dialog::DefaultSpacing();

	xPreviewDialogueText = "Origin Object: ";
	if ( s_uPreviewObjectGuid == ASURA_GUID_UNREGISTERED )
	{
		xPreviewDialogueText += "unset";
	}
	else
	{
		Tools_Object* pxObject = Tools_Object_System::GetObjectFromGuid( s_uPreviewObjectGuid );
		if ( pxObject )
		{
			xPreviewDialogueText += pxObject->GetName();
		}
		else
		{
			s_uPreviewObjectGuid = ASURA_GUID_UNREGISTERED;
			xPreviewDialogueText += "unset";
		}
	}
	pxNewDialogue = new Asura_Function_Dialog( xPreviewDialogueText.GetString(), xOffset, SetPreviewObject );
	xNewOverlay.AddDialog( pxNewDialogue );
	xOffset.y += Asura_Dialog::DefaultSpacing();

	pxNewDialogue = new Asura_Function_Dialog( "Start", xOffset, StartPreview, NULL, CanStartPreview );
	xNewOverlay.AddDialog( pxNewDialogue );
	xOffset.x += 15.0f;

	pxNewDialogue = new Asura_Function_Dialog( "Stop", xOffset, StopPreview, NULL, CanStopPreview );
	xNewOverlay.AddDialog( pxNewDialogue );
	xOffset.x -= 15.0f;
	xOffset.y += Asura_Dialog::LargeSpacing();

	if ( GetNumAnimPackageMappingFactors() > 0 )
	{
		pxNewDialogue = new Asura_Dialog( "Preview Mapping Data:", xOffset );
		xNewOverlay.AddDialog( pxNewDialogue );
		xOffset.y += Asura_Dialog::DefaultSpacing();

		AddPreviewScoringDataControls( xNewOverlay, xOffset );
	}

	pxTabsDialogue->SetSelectedTabPointer( &s_uMainOverlayActiveTab );
}

void Tools_AnimTemplates::DestroyMainOverlay()
{
	Asura_Overlay_System::CloseOverlays( GetMainOverlayID() );
}

void Tools_AnimTemplates::CreateAnimTemplateOverlay()
{
	Tools_CallbackOnClose_Overlay* pxNewOverlay;
	pxNewOverlay = new Tools_CallbackOnClose_Overlay( "Animation Template Data", false, &s_xAnimTemplateOverlayPosition );
	pxNewOverlay->SetOnClosingByCloseGadgetCallback( AnimTemplateOverlayClosedByUser );
	pxNewOverlay->m_uID = GetAnimTemplateOverlayID();

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

	Asura_Tabs_Dialog* pxTabsDialogue = new Asura_Tabs_Dialog( xOffset );
	pxTabsDialogue->SetControlID( GetAnimTemplateOverlayTabDialogueID() );
	pxNewOverlay->AddDialog( pxTabsDialogue );

	Asura_Dialog* pxDialogue = new Asura_Function_Dialog( "Close Tab", xOffset, CloseCurrentAnimTemplateTab );
	pxNewOverlay->AddDialog( pxDialogue );
	pxDialogue->SetOverlayRelativePosition(Asura_Vector_2(pxDialogue->m_xOffset.x - pxDialogue->m_xMaxCoord.x - 2.0f,xOffset.y - Asura_Dialog::DefaultSpacing()),false,true);

	float fMaxY = 0.0f;
	for ( Asura_LinkList_It< AnimTemplateTabData > xIt( s_xOpenAnimTemplateList ); !xIt.Done(); xIt.Next() )
	{
		Axon_Gamescene_AnimTemplate* pxTemplate = Axon_Gamescene_AnimTemplate_System::GetAnimTemplateByID( xIt.GetCurrent()->m_uTemplateUniqueID );
		if ( !pxTemplate ) { continue; }

		pxTabsDialogue->AddTab( pxTemplate->GetName().GetString() );
		xOffset.y = pxTabsDialogue->m_xMaxCoord.y + 0.5f;
		fMaxY = Asura_Maths::Max( fMaxY, xOffset.y );
		s_pxThis->AddAnimTemplateOverlayItems( *pxTemplate, *xIt.GetCurrent(), *pxTabsDialogue, xOffset );
		fMaxY = Asura_Maths::Max( fMaxY, xOffset.y );
	}

	xOffset.y = fMaxY + ( Asura_Dialog::LargeSpacing() - Asura_Dialog::DefaultSpacing() );

	pxDialogue = new Asura_Function_Dialog( "Copy Entries", xOffset, CopySelectedTemplateEntries );
	pxNewOverlay->AddDialog( pxDialogue );
	xOffset.y += Asura_Dialog::DefaultSpacing();

	pxDialogue = new Asura_Function_Dialog( "Paste Entries", xOffset, PasteEntriesIntoSelectedTemplate, NULL, CanPasteIntoSelectedTemplate );
	pxNewOverlay->AddDialog( pxDialogue );
	xOffset.y += Asura_Dialog::DefaultSpacing();

	pxTabsDialogue->SetSelectedTabPointer( &s_uAnimTemplateActiveTab );

	Asura_Overlay_System::SubmitNewOverlay( pxNewOverlay );
}

void Tools_AnimTemplates::AddAnimTemplateOverlayItems( Axon_Gamescene_AnimTemplate& xTemplate,
													   AnimTemplateTabData& xTabData,
													   Asura_Tabs_Dialog& xTabsDialogue,
													   Asura_Vector_2& xOffset )
{
	int iEntryIndex = 0;
	Asura_Dialog* pxDialogue = new Asura_Function_Dialog( "Insert", xOffset, AddAnimTemplateEntry, iEntryIndex );
	xTabsDialogue.AddDialog( pxDialogue );
	xOffset.y += Asura_Dialog::LargeSpacing();

	// Work out the number of entries first.
	int iNumEntries = 0;
	Axon_Gamescene_AnimTemplate_Entry* pxCurrentEntry = xTemplate.GetFirstEntry();
	while ( pxCurrentEntry )
	{
		++iNumEntries;
		pxCurrentEntry = pxCurrentEntry->GetNextEntry();
	}

	// Calculate the entry labels and work out the widest label.
	Asura_String* pxEntryLabels = new Asura_String[ iNumEntries ];
	float fWidestPanelLabel = 0.0f;
	pxCurrentEntry = xTemplate.GetFirstEntry();
	iEntryIndex = 0;
	while ( pxCurrentEntry )
	{
		Asura_String& xEntryLabel = pxEntryLabels[ iEntryIndex ];
		if ( pxCurrentEntry->ContainsAnim() )
		{
			xEntryLabel = "Anim: ";
			if ( pxCurrentEntry->GetAnimHashID() == ASURA_HASH_ID_UNSET )
			{
				xEntryLabel += "not set";
			}
			else
			{
				const char* pszAnimFilename = Asura_HashedFilename_System::GetFilename( pxCurrentEntry->GetAnimHashID() );
				if ( pszAnimFilename )
				{
					xEntryLabel += pszAnimFilename;
				}
				else
				{
					xEntryLabel += "NOT FOUND";
				}
			}
		}
		else
		{
			xEntryLabel = "Package: ";
			if ( pxCurrentEntry->GetAnimPackageUniqueID() == Axon_GS2_AnimPackage::uUNSET_ID )
			{
				xEntryLabel += "not set";
			}
			else
			{
				const Axon_GS2_AnimPackage* pxPackage = Axon_Gamescene_AnimTemplate_System::GetAnimPackageByID( pxCurrentEntry->GetAnimPackageUniqueID() );
				if ( pxPackage )
				{
					xEntryLabel += pxPackage->GetName();
				}
				else
				{
					xEntryLabel += "NOT FOUND";
				}
			}
		}

		const float fPanelLabelWidth = Asura_Text_Output::WidthOfString( xEntryLabel.GetString(), ASURA_TEXT_FORMAT_SMALLFONT );
		fWidestPanelLabel = Asura_Maths::Max( fWidestPanelLabel, fPanelLabelWidth );

		++iEntryIndex;
		pxCurrentEntry = pxCurrentEntry->GetNextEntry();
	}

	Asura_LinkList_It< bool > xEntryMinimisedIt( xTabData.m_xEntryMinimisedList );
	pxCurrentEntry = xTemplate.GetFirstEntry();
	iEntryIndex = 0;
	bool bAllowFurtherEntries = true;
	while ( pxCurrentEntry && !xEntryMinimisedIt.Done() && bAllowFurtherEntries )
	{
		// Make the panel at least wide enough to fit all the dialogues on, and wider still if we have a huge label.
		const Asura_Vector_2 xPanelSize = ( xTemplate.GetType() != AXON_ATT_FIDGETBEHAVIOUR ) ?
										  Asura_Vector_2( Asura_Maths::Max( 41.0f, fWidestPanelLabel + 23.5f ), 0.0f ) :
										  Asura_Vector_2( fWidestPanelLabel + 12.0f, 0.0f );
		Asura_Panel_Dialog* pxPanelDialogue = new Asura_Panel_Dialog( xOffset, xPanelSize, 0 );
		pxPanelDialogue->UpdateLabel( pxEntryLabels[ iEntryIndex ].GetString() );
		pxPanelDialogue->SetRenderBorder( true );
		pxPanelDialogue->EnableMinimise( xEntryMinimisedIt.GetCurrent() );
		pxPanelDialogue->SetOnMinimisedToggleCallback( RequestRefreshAnimTemplateOverlay );
		xTabsDialogue.AddDialog( pxPanelDialogue );

		Asura_Vector_2 xPanelOffset;
		xPanelOffset.x = 1.0f;
		xPanelOffset.y = 1.5f;

		AddAnimTemplateEntryOverlayItems( xTemplate, *pxCurrentEntry, iEntryIndex, *pxPanelDialogue, xPanelOffset, bAllowFurtherEntries );

		// Add the buttons so they will fit flush with the RHS of the panel.
		const float fInitialX = xOffset.x;
		xOffset.x += xPanelSize.x - 3.5f;
		xOffset.y -= 0.1f;
		if ( xTemplate.GetType() != AXON_ATT_FIDGETBEHAVIOUR && iEntryIndex > 0 )
		{
			Asura_Icon_Dialog* pxIconDlg = new Asura_Icon_Dialog( xOffset, Tools_Dialog::GetIconTexture(), TOOLS_ICON_ARROW_UP );
			pxIconDlg->SetClickedFunction( MoveAnimTemplateEntryUp, iEntryIndex );
			xTabsDialogue.AddDialog( pxIconDlg );
		}
		xOffset.x += 2.0f;

		if ( xTemplate.GetType() != AXON_ATT_FIDGETBEHAVIOUR && ( iEntryIndex < iNumEntries - 1 ) )
		{
			Asura_Icon_Dialog* pxIconDlg = new Asura_Icon_Dialog( xOffset, Tools_Dialog::GetIconTexture(), TOOLS_ICON_ARROW_DOWN );
			pxIconDlg->SetClickedFunction( MoveAnimTemplateEntryDown, iEntryIndex );
			xTabsDialogue.AddDialog( pxIconDlg );
		}
		xOffset.x += 2.0f;

		Asura_Icon_Dialog* pxIconDlg = new Asura_Icon_Dialog( xOffset, Tools_Dialog::GetIconTexture(), TOOLS_ICON_DELETE );
		pxIconDlg->SetClickedFunction( DeleteAnimTemplateEntry, iEntryIndex );
		xTabsDialogue.AddDialog( pxIconDlg );

		xOffset.x = fInitialX;
		xOffset.y = pxPanelDialogue->m_xMaxCoord.y + 1.0f;

		++iEntryIndex;

		if ( bAllowFurtherEntries && xTemplate.GetType() != AXON_ATT_FIDGETBEHAVIOUR )
		{
			pxDialogue = new Asura_Function_Dialog( "Insert", xOffset, AddAnimTemplateEntry, iEntryIndex );
			xTabsDialogue.AddDialog( pxDialogue );
			xOffset.y += Asura_Dialog::LargeSpacing();
		}

		pxCurrentEntry = pxCurrentEntry->GetNextEntry();
		xEntryMinimisedIt.Next();
	}

	// If this is a fidget template then add the fidget time dialogues underneath.
	if ( xTemplate.GetType() == AXON_ATT_FIDGETBEHAVIOUR )
	{
		Axon_Gamescene_FidgetAnimTemplate* pxFidgetTemplate = static_cast< Axon_Gamescene_FidgetAnimTemplate* >( &xTemplate );

		pxDialogue = new Asura_Numerical_Dialog( "Min Time Between Fidgets", xOffset, &pxFidgetTemplate->m_fMinFidgetDelay,
			0.0f, 999.0f, 0.25f, "%.2f", &s_bSelectedAnimTemplateChanged );
		xTabsDialogue.AddDialog( pxDialogue );
		xOffset.y += Asura_Dialog::DefaultSpacing();		

		pxDialogue = new Asura_Numerical_Dialog( "Max Time Between Fidgets", xOffset, &pxFidgetTemplate->m_fMaxFidgetDelay,
			0.0f, 999.0f, 0.25f, "%.2f", &s_bSelectedAnimTemplateChanged );
		xTabsDialogue.AddDialog( pxDialogue );
		xOffset.y += Asura_Dialog::DefaultSpacing();		
	}

	delete [] pxEntryLabels;
}

void Tools_AnimTemplates::AddAnimTemplateEntryOverlayItems( Axon_Gamescene_AnimTemplate& xTemplate,
															Axon_Gamescene_AnimTemplate_Entry& xEntry,
															int iEntryIndex,
															Asura_Panel_Dialog& xPanelDialogue,
															Asura_Vector_2& xOffset,
															bool& bAllowFurtherEntries )
{
	Asura_Dialog* pxNewDialog;

	pxNewDialog = new Asura_Function_Dialog( "Set Anim", xOffset, SetAnimTemplateEntryAnim, iEntryIndex );
	xPanelDialogue.AddDialogToPanel( pxNewDialog );
	xOffset.x += 12.5f;

	if ( GetNumAnimPackageMappingFactors() > 0 )
	{
		pxNewDialog = new Asura_Function_Dialog( "Set Package", xOffset, SetAnimTemplateEntryPackage, iEntryIndex );
		xPanelDialogue.AddDialogToPanel( pxNewDialog );
	}
	xOffset.x -= 12.5f;
	xOffset.y += Asura_Dialog::DefaultSpacing();

	// Add the flags first,
	float fOldX = xOffset.x;
	if ( xTemplate.GetType() == AXON_ATT_STATIC || xTemplate.GetType() == AXON_ATT_LOOPINGMOVEMENT )
	{
		pxNewDialog = new Asura_FlagTickbox_Dialog("Loop", xOffset, &xEntry.m_uAnimTemplateFlags, 0x1, &s_bSelectedAnimTemplateChangedRefresh);
		pxNewDialog->SetHelpText("Set this animation to loop. Otherwise it will play just once");
		xPanelDialogue.AddDialogToPanel(pxNewDialog);
		xOffset.x += 12.5f;
	}

	if ( xTemplate.GetType() == AXON_ATT_STATIC || xTemplate.GetType() == AXON_ATT_LOOPINGMOVEMENT || xTemplate.GetType() == AXON_ATT_RIGIDMOVEMENT )
	{
		pxNewDialog = new Asura_FlagTickbox_Dialog("Backwards", xOffset, &xEntry.m_uAnimTemplateFlags, 0x4, &s_bSelectedAnimTemplateChanged);
		pxNewDialog->SetHelpText("Toggle whether or not to play this animation backwards");
		xPanelDialogue.AddDialogToPanel(pxNewDialog);
		xOffset.x += 12.5f;

		pxNewDialog = new Asura_FlagTickbox_Dialog("Mirror", xOffset, &xEntry.m_uAnimTemplateFlags, 0x20, &s_bSelectedAnimTemplateChanged);
		pxNewDialog->SetHelpText("Toggle whether or not to play this animation mirrored");
		xPanelDialogue.AddDialogToPanel(pxNewDialog);
		xOffset.x = fOldX;
		xOffset.y += Asura_Dialog::DefaultSpacing();
	}

	pxNewDialog = new Asura_FlagTickbox_Dialog("Tween", xOffset, &xEntry.m_uAnimTemplateFlags, 0x2, &s_bSelectedAnimTemplateChangedRefresh);
	pxNewDialog->SetHelpText("Toggle whether or not to tween into this animation");
	xPanelDialogue.AddDialogToPanel(pxNewDialog);
	xOffset.x += 12.5f;

	if ( xEntry.m_bTween )
	{
		pxNewDialog = new Asura_Numerical_Dialog("Tween in Time (seconds)", xOffset, &xEntry.m_fTweenTime, 0.0f, 10.0f, 0.1f, "%.1f", &s_bSelectedAnimTemplateChanged);
		pxNewDialog->SetHelpText("Set the time taken to tween into this animation");
		xPanelDialogue.AddDialogToPanel(pxNewDialog);
	}
	xOffset.x = fOldX;
	xOffset.y += Asura_Dialog::DefaultSpacing();

	switch (xTemplate.m_uTemplateType)
	{
		case AXON_ATT_STATIC:
		{
			if (xEntry.m_bLoop)
			{
				Axon_Gamescene_StaticAnimTemplate_Entry *pxStatic = static_cast<Axon_Gamescene_StaticAnimTemplate_Entry*>(&xEntry);
				if (pxStatic->m_bLoopTimeInSeconds)
				{
					pxNewDialog = new Asura_Numerical_Dialog("Duration (Seconds)",xOffset,&pxStatic->m_fUserSetDuration, 0, 600, 0.1f, "%.1f", &s_bSelectedAnimTemplateChangedRefresh);
					pxNewDialog->SetHelpText("Set the duration in seconds to play this animation for (Zero means permanent)");
				}
				else
				{
					pxNewDialog = new Asura_Numerical_Dialog("Number of repeats",xOffset,&pxStatic->m_fUserSetDuration, 0, 600, 0.1f, "%.1f", &s_bSelectedAnimTemplateChangedRefresh);
					pxNewDialog->SetHelpText("Set the number of times to loop this animation (Zero means permanent)");
				}
				xPanelDialogue.AddDialogToPanel(pxNewDialog);
				xOffset.x += 25.0f;
				pxNewDialog = new Asura_FlagTickbox_Dialog("Loop time in seconds", xOffset, &xEntry.m_uAnimTemplateFlags, 0x8, &s_bSelectedAnimTemplateChanged);
				pxNewDialog->SetHelpText("Toggle whether to loop time in seconds or by number of loops");
				xPanelDialogue.AddDialogToPanel(pxNewDialog);
				xOffset.x -= 25.0f;
				xOffset.y += Asura_Dialog::DefaultSpacing();

				if (pxStatic->m_fUserSetDuration == 0.0f)
				{
					Asura_String xFidgetText( "Fidget Template : " );
					if ( pxStatic->GetFidgetBehaviourID() == uAXON_ANIMATION_SEQUENCE_ID_NONE )
					{
						xFidgetText += "None";
					}
					else
					{
						const Axon_Gamescene_AnimTemplate* pxTemplate = Axon_Gamescene_AnimTemplate_System::GetAnimTemplateByID( pxStatic->GetFidgetBehaviourID() );
						if ( pxTemplate )
						{
							xFidgetText += pxTemplate->GetName();
						}
						else
						{
							xFidgetText += "NOT FOUND";
						}
					}
					pxNewDialog = new Asura_Function_Dialog(xFidgetText.GetString(), xOffset, ChooseFidgetAnimTemplate, iEntryIndex);
					pxNewDialog->SetHelpText("Set the fidget behaviour to use while playing this animation");
					xPanelDialogue.AddDialogToPanel(pxNewDialog);
					xOffset.y += Asura_Dialog::DefaultSpacing();

					bAllowFurtherEntries = false;
				}
				else
				{
					bAllowFurtherEntries = true;
				}
			}
			else
			{
				bAllowFurtherEntries = true;
			}
			break;
		}

		case AXON_ATT_LOOPINGMOVEMENT:
		{
			if (xEntry.m_bLoop)
			{
				Axon_Gamescene_LoopingAnimTemplate_Entry *pxLooping = static_cast<Axon_Gamescene_LoopingAnimTemplate_Entry*>(&xEntry);
				if (pxLooping->m_bLoopTimeInSeconds)
				{
					pxNewDialog = new Asura_Numerical_Dialog("Duration",xOffset,&pxLooping->m_fUserSetDuration, 0, 600, 0.1f, "%.1f", &s_bSelectedAnimTemplateChangedRefresh);
					pxNewDialog->SetHelpText("Set the duration in seconds to play this animation for (Zero means permanent)");
				}
				else
				{
					pxNewDialog = new Asura_Numerical_Dialog("Number of repeats",xOffset,&pxLooping->m_fUserSetDuration, 0, 600, 0.1f, "%.1f", &s_bSelectedAnimTemplateChangedRefresh);
					pxNewDialog->SetHelpText("Set the number of times to loop this animation (Zero means permanent)");
				}
				xPanelDialogue.AddDialogToPanel(pxNewDialog);
				xOffset.x += 25.0f;
				pxNewDialog = new Asura_FlagTickbox_Dialog("Loop time in seconds", xOffset, &xEntry.m_uAnimTemplateFlags, 0x8, &s_bSelectedAnimTemplateChanged);
				pxNewDialog->SetHelpText("Toggle whether to loop time in seconds or by number of loops");
				xOffset.x -= 25.0f;
				xPanelDialogue.AddDialogToPanel(pxNewDialog);
				xOffset.y += Asura_Dialog::DefaultSpacing();

				bAllowFurtherEntries = (pxLooping->m_fUserSetDuration != 0.0f);
			}
			else
			{
				bAllowFurtherEntries = true;
			}
			break;
		}

		case AXON_ATT_RIGIDMOVEMENT:
		{
			bAllowFurtherEntries = true;
			break;
		}

		case AXON_ATT_FIDGETBEHAVIOUR:
		{
			Axon_Gamescene_FidgetAnimTemplate_Entry *pxFidget = (Axon_Gamescene_FidgetAnimTemplate_Entry*)(&xEntry);
			pxNewDialog = new Asura_Numerical_Dialog("Fidget Weight",xOffset,&pxFidget->m_fFidgetWeight, 0, 600, 0.1f, "%.1f", &s_bSelectedAnimTemplateChanged);
			pxNewDialog->SetHelpText("Set the weight for this fidget");
			xPanelDialogue.AddDialogToPanel(pxNewDialog);
			xOffset.y += Asura_Dialog::DefaultSpacing();

			bAllowFurtherEntries = true;
			break;
		}

		default:
			Asura_Assert(false, "Unknown animation template type");
			bAllowFurtherEntries = false;
			break;
	}
}

void Tools_AnimTemplates::CloseCurrentAnimTemplateTab()
{
	Asura_Overlay* pxAnimTemplateOverlay = Asura_Overlay_System::GetOverlay( GetAnimTemplateOverlayID() );
	if ( !pxAnimTemplateOverlay ) { return; }
	Asura_Tabs_Dialog* pxTabsDialogue = reinterpret_cast< Asura_Tabs_Dialog* >( pxAnimTemplateOverlay->GetDialog( GetAnimTemplateOverlayTabDialogueID() ) );
	if ( !pxTabsDialogue ) { return; }

	const Asura_String xTabName( pxTabsDialogue->GetSelectedTabName() );

	for ( Asura_LinkList_It< AnimTemplateTabData > xIt( s_xOpenAnimTemplateList ); !xIt.Done(); xIt.Next() )
	{
		Axon_Gamescene_AnimTemplate* pxTemplate = Axon_Gamescene_AnimTemplate_System::GetAnimTemplateByID( xIt.GetCurrent()->m_uTemplateUniqueID );
		if ( pxTemplate && pxTemplate->GetName() == xTabName )
		{
			s_xOpenAnimTemplateList.Remove( *xIt.GetCurrent() );
			break;
		}
	}

	pxTabsDialogue->DeleteTab( pxTabsDialogue->GetSelected() );

	if ( pxTabsDialogue->GetNumTabs() == 0 )
	{
		Asura_Overlay_System::CloseOverlays( GetAnimTemplateOverlayID() );
	}
	else
	{
		RequestRefreshAnimTemplateOverlay();
		RequestRefreshAnimTemplateTreeSelection();
	}
}

void Tools_AnimTemplates::DestroyAnimTemplateOverlay()
{
	Asura_Overlay_System::CloseOverlays( GetAnimTemplateOverlayID() );
}

void Tools_AnimTemplates::CreateAnimPackageOverlay()
{
	Tools_CallbackOnClose_Overlay* pxNewOverlay;
	pxNewOverlay = new Tools_CallbackOnClose_Overlay( "Animation Package Data", false, &s_xAnimPackageOverlayPosition );
	pxNewOverlay->SetOnClosingByCloseGadgetCallback( AnimPackageOverlayClosedByUser );
	pxNewOverlay->m_uID = GetAnimPackageOverlayID();

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

	Asura_Tabs_Dialog* pxTabsDialogue = new Asura_Tabs_Dialog( xOffset );
	pxTabsDialogue->SetControlID( GetAnimPackageOverlayTabDialogueID() );
	pxNewOverlay->AddDialog( pxTabsDialogue );

	Asura_Dialog* pxDialogue = new Asura_Function_Dialog( "Close Tab", xOffset, CloseCurrentAnimPackageTab );
	pxNewOverlay->AddDialog( pxDialogue );
	pxDialogue->SetOverlayRelativePosition(Asura_Vector_2(pxDialogue->m_xOffset.x - pxDialogue->m_xMaxCoord.x - 2.0f,xOffset.y - Asura_Dialog::DefaultSpacing()),false,true);

	float fMaxY = 0.0f;
	for ( Asura_LinkList_It< u_int > xIt( s_xOpenAnimPackageList ); !xIt.Done(); xIt.Next() )
	{
		Axon_GS2_AnimPackage* pxPackage = Axon_Gamescene_AnimTemplate_System::GetAnimPackageByID( *xIt.GetCurrent() );
		if ( !pxPackage ) { continue; }

		pxTabsDialogue->AddTab( pxPackage->GetName().GetString() );
		xOffset.y = pxTabsDialogue->m_xMaxCoord.y + 0.5f;
		fMaxY = Asura_Maths::Max( fMaxY, xOffset.y );
		s_pxThis->AddAnimPackageOverlayItems( *pxPackage, *pxTabsDialogue, xOffset );
		fMaxY = Asura_Maths::Max( fMaxY, xOffset.y );
	}

	xOffset.y = fMaxY + ( Asura_Dialog::LargeSpacing() - Asura_Dialog::DefaultSpacing() );

	pxTabsDialogue->SetSelectedTabPointer( &s_uAnimPackageActiveTab );

	Asura_Overlay_System::SubmitNewOverlay( pxNewOverlay );
}

void Tools_AnimTemplates::AddAnimPackageOverlayItems( Axon_GS2_AnimPackage& xPackage, Asura_Tabs_Dialog& xTabsDialogue, Asura_Vector_2& xOffset )
{
	const u_int uNumMappings = xPackage.GetNumMappings();
	if ( uNumMappings == 0 ) { return; }

	const Asura_Vector_2 xTableOffset( xOffset.x, xOffset.y );
	static const Asura_Vector_2	s_xMinCellSize( 6.0f, 1.2f );
	static const Asura_Vector_2 s_xMaxTableSize( 100.0f, 21.0f );
	Asura_Table_Dialog* pxTableDialogue = new Asura_Table_Dialog( xTableOffset, s_xMaxTableSize.x, s_xMaxTableSize.y, true );
	pxTableDialogue->SetCellSpacing( Asura_Vector_2( pxTableDialogue->GetCellSpacing().x, 0.25f ) );
	xTabsDialogue.AddDialog( pxTableDialogue );

	pxTableDialogue->CreateTable( s_xMinCellSize, uNumMappings, GetNumAnimPackageMappingFactors() + 2, CreateTableCell,
		static_cast< void* >( &xPackage ),
		NULL, s_pxAnimPackageTableHeadings, NULL, NULL, NULL, NULL );
	//pxTableDialogue->EnableClickColumnHeadingsToSort();

	xOffset.y += pxTableDialogue->GetTableSize().y + Asura_Dialog::SmallSpacing();
}

void Tools_AnimTemplates::CloseCurrentAnimPackageTab()
{
	Asura_Overlay* pxAnimPackageOverlay = Asura_Overlay_System::GetOverlay( GetAnimPackageOverlayID() );
	if ( !pxAnimPackageOverlay ) { return; }
	Asura_Tabs_Dialog* pxTabsDialogue = reinterpret_cast< Asura_Tabs_Dialog* >( pxAnimPackageOverlay->GetDialog( GetAnimPackageOverlayTabDialogueID() ) );
	if ( !pxTabsDialogue ) { return; }

	const Asura_String xTabName( pxTabsDialogue->GetSelectedTabName() );

	for ( Asura_LinkList_It< u_int > xIt( s_xOpenAnimPackageList ); !xIt.Done(); xIt.Next() )
	{
		Axon_GS2_AnimPackage* pxPackage = Axon_Gamescene_AnimTemplate_System::GetAnimPackageByID( *xIt.GetCurrent() );
		if ( pxPackage && pxPackage->GetName() == xTabName )
		{
			s_xOpenAnimPackageList.Remove( *xIt.GetCurrent() );
			break;
		}
	}

	pxTabsDialogue->DeleteTab( pxTabsDialogue->GetSelected() );

	if ( pxTabsDialogue->GetNumTabs() == 0 )
	{
		Asura_Overlay_System::CloseOverlays( GetAnimPackageOverlayID() );
	}
	else
	{
		RequestRefreshAnimPackageOverlay();
		RequestRefreshAnimPackageTreeSelection();
	}
}

void Tools_AnimTemplates::DestroyAnimPackageOverlay()
{
	Asura_Overlay_System::CloseOverlays( GetAnimPackageOverlayID() );
}

void Tools_AnimTemplates::ContextMenuCallback( Tools_ContextMenu& xMenu, const Tools_ContextMenu_Context& xContext )
{
	s_pxThis->AddContextMenuItems( xMenu, xContext );
}

void Tools_AnimTemplates::AddContextMenuItems( Tools_ContextMenu& xMenu, const Tools_ContextMenu_Context& xContext )
{
	Tools_ContextMenu_Section* pxSection = xMenu.GetSection( TOOLS_CONTEXTMENU_SECTION_SELECTION );

	Tools_ContextMenu_Item* pxItem = NULL;

	Asura_Overlay* pxMainOverlay = Asura_Overlay_System::GetOverlay( GetMainOverlayID() );
	if ( !pxMainOverlay ) { return; }
	Asura_Tabs_Dialog* pxTabsDialogue = static_cast< Asura_Tabs_Dialog* >( pxMainOverlay->GetDialog( GetMainOverlayTabDialogueID() ) );
	if ( !pxTabsDialogue ) { return; }

	if ( pxTabsDialogue->GetSelected() == TN_ANIM_TEMPLATES )
	{
		if ( AnimTemplateTreeTemplateItemIsSelected() )
		{
			pxItem = new Tools_ContextMenu_Item_Function( "Edit Template", EditAnimTemplate );
			pxSection->AddItem( pxItem );
		}
		if ( FidgetTemplateTreeItemIsSelected() )
		{
			pxItem = new Tools_ContextMenu_Item_Function( "Create New Fidget Template", NewAnimTemplate );
			pxSection->AddItem( pxItem );
		}
		else if ( LoopingTemplateTreeItemIsSelected() )
		{
			pxItem = new Tools_ContextMenu_Item_Function( "Create New Looping Template", NewAnimTemplate );
			pxSection->AddItem( pxItem );
		}
		else if ( RigidTemplateTreeItemIsSelected() )
		{
			pxItem = new Tools_ContextMenu_Item_Function( "Create New Rigid Template", NewAnimTemplate );
			pxSection->AddItem( pxItem );
		}
		else if ( StaticTemplateTreeItemIsSelected() )
		{
			pxItem = new Tools_ContextMenu_Item_Function( "Create New Static Template", NewAnimTemplate );
			pxSection->AddItem( pxItem );
		}
		if ( AnyTemplateTreeItemIsSelected() )
		{
			pxItem = new Tools_ContextMenu_Item_Function( "Create New Folder", NewAnimTemplateFolder );
			pxSection->AddItem( pxItem );
		}
		if ( LocalWritableTemplateTreeItemIsSelected() )
		{
			pxItem = new Tools_ContextMenu_Item_Function( "Delete Template", DeleteAnimTemplate );
			pxSection->AddItem( pxItem );
		}
		if ( EmptyFolderTemplateTreeItemIsSelected() )
		{
			pxItem = new Tools_ContextMenu_Item_Function( "Delete Folder", DeleteAnimTemplateFolder );
			pxSection->AddItem( pxItem );
		}
	}
	else
	{
		if ( AnimPackageTreeItemIsSelected() )
		{
			pxItem = new Tools_ContextMenu_Item_Function( "Edit Package", EditAnimPackage );
			pxSection->AddItem( pxItem );
		}
		if ( AnyPackageTreeItemIsSelected() )
		{
			pxItem = new Tools_ContextMenu_Item_Function( "Create New Package", NewAnimPackage );
			pxSection->AddItem( pxItem );
		}
		if ( AnyPackageTreeItemIsSelected() )
		{
			pxItem = new Tools_ContextMenu_Item_Function( "Create New Folder", NewAnimPackageFolder );
			pxSection->AddItem( pxItem );
		}
		if ( LocalWritablePackageTreeItemIsSelected() )
		{
			pxItem = new Tools_ContextMenu_Item_Function( "Delete Package", DeleteAnimPackage );
			pxSection->AddItem( pxItem );
		}
		if ( EmptyFolderPackageTreeItemIsSelected() )
		{
			pxItem = new Tools_ContextMenu_Item_Function( "Delete Folder", DeleteAnimPackageFolder );
			pxSection->AddItem( pxItem );
		}
	}
}

void Tools_AnimTemplates::MainOverlayClosedByUser()
{
	s_pxThis->DestroyAnimTemplateOverlay();
	s_pxThis->DestroyAnimPackageOverlay();
}

void Tools_AnimTemplates::AnimTemplateOverlayClosedByUser()
{
	s_xOpenAnimTemplateList.Clear();
	s_uAnimTemplateActiveTabPrev = 0;
	s_uAnimTemplateActiveTab = 0;
}

void Tools_AnimTemplates::AnimPackageOverlayClosedByUser()
{
	s_xOpenAnimPackageList.Clear();
	s_uAnimPackageActiveTabPrev = 0;
	s_uAnimPackageActiveTab = 0;
}

void Tools_AnimTemplates::SetPreviewDefaultSkin()
{
	Asura_String xSkinFilename;
	Tools_Animation_Files::ChooseSkin( &s_xPreviewDefaultSkin, &xSkinFilename );
	RequestRefreshMainOverlay();
}

class Tools_ChooseObject_Anything : public Tools_ChooseObject
{
public:
	Tools_ChooseObject_Anything( void (*pfnCallback)(const Asura_Guid uGuid) ) : m_pfnCallback( pfnCallback ) {}
	virtual void ObjectSelected( Tools_Object* pxObject ) { if ( m_pfnCallback && pxObject ) { m_pfnCallback( pxObject->GetGuid() ); } }

private:
	void (*m_pfnCallback)(const Asura_Guid uGuid);
};

void Tools_AnimTemplates::SetPreviewObject()
{
	Tools_Object_System::ChooseObject( "Pick Object To Preview From", new Tools_ChooseObject_Anything( SetPreviewObjectToThis ) );
}

void Tools_AnimTemplates::SetPreviewObjectToThis( const Asura_Guid uGuid )
{
	s_pxThis->s_uPreviewObjectGuid = uGuid;
	RequestRefreshMainOverlay();
}

void Tools_AnimTemplates::StartPreview()
{
	const Asura_TreeDialog_Item* pxCurrent = s_xAnimTemplateTree.GetSelectedItem();
	if ( !pxCurrent || pxCurrent->GetUserData() == uAXON_ANIMATION_SEQUENCE_ID_NONE ) { return; }
	if ( s_bPreviewActive || s_xPreviewDefaultSkin.IsBlank() || s_uPreviewObjectGuid == ASURA_GUID_UNREGISTERED ) { return; }

	Axon_Gamescene_AnimTemplate* pxTemplate = Axon_Gamescene_AnimTemplate_System::GetAnimTemplateByID( pxCurrent->GetUserData() );
	if ( !pxTemplate ) { return; }

	Tools_Object* pxObject = Tools_Object_System::GetObjectFromGuid( s_uPreviewObjectGuid );
	if ( !pxObject ) { return; }

	Tools_AnimTemplates_Play::Play( s_xPreviewDefaultSkin, ASURA_HASH_ID_UNSET, ASURA_HASH_ID_UNSET, s_pxThis->GetPreviewScoringData(),
									pxTemplate, pxObject->Position, pxObject->Orientation );
	s_bPreviewActive = true;
}

void Tools_AnimTemplates::StopPreview()
{
	Tools_AnimTemplates_Play::Stop();
	s_bPreviewActive = false;
}

bool Tools_AnimTemplates::CanStartPreview()
{
	const Asura_TreeDialog_Item* pxCurrent = s_xAnimTemplateTree.GetSelectedItem();
	if ( !pxCurrent || pxCurrent->GetUserData() == uAXON_ANIMATION_SEQUENCE_ID_NONE ) { return false; }
	return ( !s_bPreviewActive && !s_xPreviewDefaultSkin.IsBlank() && s_uPreviewObjectGuid != ASURA_GUID_UNREGISTERED );
}

bool Tools_AnimTemplates::CanStopPreview()
{
	return s_bPreviewActive;
}

void Tools_AnimTemplates::CreateAnimTemplateTree()
{
	// We make sure we can build the tree from scratch and from partially complete, so new items can be added into an existing tree.
	for ( Axon_GS2_AnimTemplate_Iterator xTemplateIter; !xTemplateIter.Done(); xTemplateIter.Next() )
	{
		const Axon_Gamescene_AnimTemplate* pxTemplate = xTemplateIter.GetCurrent();

		if ( !s_xAnimTemplateTree.FindByData( pxTemplate->GetUniqueID() ) )
		{
			Asura_String xFullFileName;
			xFullFileName = pxTemplate->GetPath() + "\\";
			xFullFileName.Replace( szTEMPLATES_DIRECTORY, "" );
			xFullFileName += pxTemplate->GetName();
			s_xAnimTemplateTree.AddFilePath( xFullFileName.GetString(), pxTemplate->GetUniqueID(), uAXON_ANIMATION_SEQUENCE_ID_NONE );
		}
	}

	List< Asura_String > xDirectoryList;
	static const char* szTemplatesDirectoryNoSlash = "AnimTemplates\\Templates";
	Tools_FileSystem::FindDirectories( szTemplatesDirectoryNoSlash, xDirectoryList, TOOLS_FILEFIND_RECURSIVE | TOOLS_FILEFIND_IGNOREBACKUPDIR );
	for ( CLIF< Asura_String > xLIF( &xDirectoryList ); !xLIF.done(); xLIF.next() )
	{
		Asura_String xPath = xLIF().GetString();
		xPath.Replace( szTemplatesDirectoryNoSlash, "" );
		int iFirstSlash = 0;
		if ( xPath.Contains( "\\", &iFirstSlash ) && iFirstSlash == 0 )
		{
			xPath.Replace( "\\", "" );
		}
		if ( !xPath.IsBlank() )
		{
			s_xAnimTemplateTree.AddFolderPath( xPath.GetString(), uAXON_ANIMATION_SEQUENCE_ID_NONE );
		}
	}
}

void Tools_AnimTemplates::SetAnimTemplateTreeModifiedColours()
{
	const bool bMainOverlayIsOnTop = ( Asura_Overlay_System::GetTopOverlayID() == GetMainOverlayID() );

	for ( Asura_LinkList_It< u_int > xModifiedIter( s_xAnimTemplateModifiedList ); !xModifiedIter.Done(); xModifiedIter.Next() )
	{
		Asura_TreeDialog_Item* pxModifiedItem = s_xAnimTemplateTree.FindByData( *xModifiedIter.GetCurrent() );
		while ( pxModifiedItem )
		{
			pxModifiedItem->SetColourOverride( bMainOverlayIsOnTop ? 0xffff0000 : 0xffB03434 );
			pxModifiedItem = pxModifiedItem->GetParent();
		}
	}
}

void Tools_AnimTemplates::ResetAnimTemplateTreeColours()
{
	ResetTreeItemAndChildrensColour( s_xAnimTemplateTree.GetRoot() );
}

void Tools_AnimTemplates::ResetTreeItemAndChildrensColour( Asura_TreeDialog_Item* pxItem )
{
	pxItem->SetColourOverride( 0 );

	Asura_TreeDialog_Item* pxChild = pxItem->GetChild();
	while ( pxChild )
	{
		ResetTreeItemAndChildrensColour( pxChild );
		pxChild = pxChild->GetSibling();
	}
}

void Tools_AnimTemplates::AnimTemplateTreeDoubleClicked()
{
	EditAnimTemplate();
}

bool Tools_AnimTemplates::AnyTemplateTreeItemIsSelected()
{
	return ( s_xAnimTemplateTree.GetSelectedItem() != NULL );
}

bool Tools_AnimTemplates::AnimTemplateTreeTemplateItemIsSelected()
{
	const Asura_TreeDialog_Item* pxCurrentItem = s_xAnimTemplateTree.GetSelectedItem();
	return ( pxCurrentItem && pxCurrentItem->GetUserData() != uAXON_ANIMATION_SEQUENCE_ID_NONE );
}

bool Tools_AnimTemplates::FidgetTemplateTreeItemIsSelected()
{
	const Asura_TreeDialog_Item* pxCurrentItem = s_xAnimTemplateTree.GetSelectedItem();
	while ( pxCurrentItem && pxCurrentItem->GetName() )
	{
		if ( strcmp( pxCurrentItem->GetName(), szFIDGET ) == 0 )
		{
			return true;
		}
		pxCurrentItem = pxCurrentItem->GetParent();
	}
	return false;
}

bool Tools_AnimTemplates::LoopingTemplateTreeItemIsSelected()
{
	const Asura_TreeDialog_Item* pxCurrentItem = s_xAnimTemplateTree.GetSelectedItem();
	while ( pxCurrentItem && pxCurrentItem->GetName() )
	{
		if ( strcmp( pxCurrentItem->GetName(), szLOOPING ) == 0 )
		{
			return true;
		}
		pxCurrentItem = pxCurrentItem->GetParent();
	}
	return false;
}

bool Tools_AnimTemplates::RigidTemplateTreeItemIsSelected()
{
	const Asura_TreeDialog_Item* pxCurrentItem = s_xAnimTemplateTree.GetSelectedItem();
	while ( pxCurrentItem && pxCurrentItem->GetName() )
	{
		if ( strcmp( pxCurrentItem->GetName(), szRIGID ) == 0 )
		{
			return true;
		}
		pxCurrentItem = pxCurrentItem->GetParent();
	}
	return false;
}

bool Tools_AnimTemplates::StaticTemplateTreeItemIsSelected()
{
	const Asura_TreeDialog_Item* pxCurrentItem = s_xAnimTemplateTree.GetSelectedItem();
	while ( pxCurrentItem && pxCurrentItem->GetName() )
	{
		if ( strcmp( pxCurrentItem->GetName(), szSTATIC ) == 0 )
		{
			return true;
		}
		pxCurrentItem = pxCurrentItem->GetParent();
	}
	return false;
}

bool Tools_AnimTemplates::LocalWritableTemplateTreeItemIsSelected()
{
	const Asura_TreeDialog_Item* pxCurrentItem = s_xAnimTemplateTree.GetSelectedItem();
	if ( !pxCurrentItem || pxCurrentItem->GetUserData() == uAXON_ANIMATION_SEQUENCE_ID_NONE ) { return false; }

	const Axon_Gamescene_AnimTemplate* pxTemplate = Axon_Gamescene_AnimTemplate_System::GetAnimTemplateByID( pxCurrentItem->GetUserData() );
	if ( !pxTemplate ) { return false; }

	return pxTemplate->IsLocalOnlyAndWritable();
}

bool Tools_AnimTemplates::EmptyFolderTemplateTreeItemIsSelected()
{
	const Asura_TreeDialog_Item* pxCurrentItem = s_xAnimTemplateTree.GetSelectedItem();
	return ( pxCurrentItem && pxCurrentItem->GetUserData() == uAXON_ANIMATION_SEQUENCE_ID_NONE && !pxCurrentItem->GetChild() );
}

bool Tools_AnimTemplates::CanPasteIntoSelectedTemplate()
{
	const Axon_Gamescene_AnimTemplate* pxTemplate = GetSelectedAnimTemplate();
	if ( !pxTemplate ) { return false; }
	return ( s_pxAnimTemplateCopyBufferEntry != NULL && s_uAnimTemplateCopyBufferType == pxTemplate->GetType() );
}

void Tools_AnimTemplates::CreateAnimPackageTree()
{
	// We make sure we can build the tree from scratch and from partially complete, so new items can be added into an existing tree.
	for ( Axon_GS2_AnimPackage_Iterator xPackageIter; !xPackageIter.Done(); xPackageIter.Next() )
	{
		const Axon_GS2_AnimPackage* pxPackage = xPackageIter.GetCurrent();

		if ( !s_xAnimPackageTree.FindByData( pxPackage->GetUniqueID() ) )
		{
			Asura_String xFullFileName;
			xFullFileName = pxPackage->GetPath() + "\\";
			xFullFileName.Replace( szPACKAGES_DIRECTORY, "" );
			xFullFileName += pxPackage->GetName();
			s_xAnimPackageTree.AddFilePath( xFullFileName.GetString(), pxPackage->GetUniqueID(), Axon_GS2_AnimPackage::uUNSET_ID );
		}
	}

	List< Asura_String > xDirectoryList;
	static const char* szPackagesDirectoryNoSlash = "AnimTemplates\\Packages";
	Tools_FileSystem::FindDirectories( szPackagesDirectoryNoSlash, xDirectoryList, TOOLS_FILEFIND_RECURSIVE | TOOLS_FILEFIND_IGNOREBACKUPDIR );
	for ( CLIF< Asura_String > xLIF( &xDirectoryList ); !xLIF.done(); xLIF.next() )
	{
		Asura_String xPath = xLIF().GetString();
		xPath.Replace( szPackagesDirectoryNoSlash, "" );
		int iFirstSlash = 0;
		if ( xPath.Contains( "\\", &iFirstSlash ) && iFirstSlash == 0 )
		{
			xPath.Replace( "\\", "" );
		}
		if ( !xPath.IsBlank() )
		{
			s_xAnimPackageTree.AddFolderPath( xPath.GetString(), Axon_GS2_AnimPackage::uUNSET_ID );
		}
	}
}

void Tools_AnimTemplates::SetAnimPackageTreeModifiedColours()
{
	const bool bMainOverlayIsOnTop = ( Asura_Overlay_System::GetTopOverlayID() == GetMainOverlayID() );

	for ( Asura_LinkList_It< u_int > xModifiedIter( s_xAnimPackageModifiedList ); !xModifiedIter.Done(); xModifiedIter.Next() )
	{
		Asura_TreeDialog_Item* pxModifiedItem = s_xAnimPackageTree.FindByData( *xModifiedIter.GetCurrent() );
		while ( pxModifiedItem )
		{
			pxModifiedItem->SetColourOverride( bMainOverlayIsOnTop ? 0xffff0000 : 0xffB03434 );
			pxModifiedItem = pxModifiedItem->GetParent();
		}
	}
}

void Tools_AnimTemplates::ResetAnimPackageTreeColours()
{
	ResetTreeItemAndChildrensColour( s_xAnimPackageTree.GetRoot() );
}

void Tools_AnimTemplates::AnimPackageTreeDoubleClicked()
{
	EditAnimPackage();
}

bool Tools_AnimTemplates::AnyPackageTreeItemIsSelected()
{
	return ( s_xAnimPackageTree.GetSelectedItem() != NULL );
}

bool Tools_AnimTemplates::AnimPackageTreeItemIsSelected()
{
	const Asura_TreeDialog_Item* pxCurrentItem = s_xAnimPackageTree.GetSelectedItem();
	return ( pxCurrentItem && pxCurrentItem->GetUserData() != Axon_GS2_AnimPackage::uUNSET_ID );
}

bool Tools_AnimTemplates::LocalWritablePackageTreeItemIsSelected()
{
	const Asura_TreeDialog_Item* pxCurrentItem = s_xAnimPackageTree.GetSelectedItem();
	if ( !pxCurrentItem || pxCurrentItem->GetUserData() == Axon_GS2_AnimPackage::uUNSET_ID ) { return false; }

	const Axon_GS2_AnimPackage* pxPackage = Axon_Gamescene_AnimTemplate_System::GetAnimPackageByID( pxCurrentItem->GetUserData() );
	if ( !pxPackage ) { return false; }

	return pxPackage->IsLocalOnlyAndWritable();
}

bool Tools_AnimTemplates::EmptyFolderPackageTreeItemIsSelected()
{
	const Asura_TreeDialog_Item* pxCurrentItem = s_xAnimPackageTree.GetSelectedItem();
	return ( pxCurrentItem && pxCurrentItem->GetUserData() == Axon_GS2_AnimPackage::uUNSET_ID && !pxCurrentItem->GetChild() );
}

Asura_TableCell_Dialog* Tools_AnimTemplates::CreateAnimPackageTableCell( Asura_Table_Dialog& xTableDialog,
																		 const Asura_Vector_2& xOffset,
																		 const Asura_Vector_2& xMinCellSize,
																		 const u_int uRow,
																		 const u_int uColumn,
																		 void* const pxUserData )
{
	if ( uColumn == GetNumAnimPackageMappingFactors() )
	{
		// This is the anim file column.
		Axon_GS2_AnimPackage* pxAnimPackage = static_cast< Axon_GS2_AnimPackage* >( pxUserData );
		return new Tools_TableCell_Animation( pxAnimPackage, xTableDialog, xOffset, xMinCellSize, uRow, uColumn );
	}
	else if ( uColumn == GetNumAnimPackageMappingFactors() + 1 )
	{
		// This is the controls column.
		Axon_GS2_AnimPackage* pxAnimPackage = static_cast< Axon_GS2_AnimPackage* >( pxUserData );
		return new Tools_TableCell_ControlButtons( pxAnimPackage, xTableDialog, xOffset, xMinCellSize, uRow, uColumn );
	}
	else
	{
		// We must create something or things will crash, but this is not expected.
		return Asura_TableCell_TextDialog::Create( xTableDialog, "Unexpected column", xOffset, xMinCellSize, uRow, uColumn );
	}
}

void Tools_AnimTemplates::NewAnimTemplate()
{
	Asura_TreeDialog_Item* pxFolderItem = s_xAnimTemplateTree.GetSelectedItem();
	if ( !pxFolderItem ) { return; }
	if ( pxFolderItem->GetUserData() != uAXON_ANIMATION_SEQUENCE_ID_NONE ) { pxFolderItem = pxFolderItem->GetParent(); }
	if ( !pxFolderItem ) { return; }

	Asura_String xTemplateName;
	if ( !GetUniqueTemplateName( xTemplateName ) ) { return; }

	Axon_Gamescene_AnimTemplate* pxTemplate = NULL;
	if ( FidgetTemplateTreeItemIsSelected() )
	{
		pxTemplate = new Axon_Gamescene_FidgetAnimTemplate( xTemplateName );
	}
	else if ( LoopingTemplateTreeItemIsSelected() )
	{
		pxTemplate = new Axon_Gamescene_LoopingAnimTemplate( xTemplateName );
	}
	else if ( RigidTemplateTreeItemIsSelected() )
	{
		pxTemplate = new Axon_Gamescene_RigidAnimTemplate( xTemplateName );
	}
	else if ( StaticTemplateTreeItemIsSelected() )
	{
		pxTemplate = new Axon_Gamescene_StaticAnimTemplate( xTemplateName );
	}
	if ( !pxTemplate ) { return; }

	Asura_String xPath;
	pxFolderItem->ConcatenatePath( xPath );
	xPath.Prepend( szTEMPLATES_DIRECTORY );
	pxTemplate->SetPath( xPath );
	pxTemplate->SetLocalOnlyAndWritable( true );

	Axon_Gamescene_AnimTemplate_System::AddAnimTemplate( pxTemplate );

	// Add the new template to the main tree and select it.
	Asura_TreeDialog_Item* pxTemplateItem = s_xAnimTemplateTree.InsertItem( CompareTemplateItems,
																			pxFolderItem,
																			pxTemplate->GetName().GetString(),
																			pxTemplate->GetUniqueID() );
	s_xAnimTemplateTree.SetSelectedItem( pxTemplateItem );

	// Add the new template to the anim template overlay.
	AnimTemplateTabData xTabData;
	xTabData.m_uTemplateUniqueID = pxTemplate->GetUniqueID();
	s_xOpenAnimTemplateList.Push_Back( xTabData );
	RequestRefreshAnimTemplateOverlay();

	SetAnimTemplateModified( pxTemplate->GetUniqueID() );
}

void Tools_AnimTemplates::EditAnimTemplate()
{
	const Asura_TreeDialog_Item* pxCurrentItem = s_xAnimTemplateTree.GetSelectedItem();
	if ( !pxCurrentItem || pxCurrentItem->GetUserData() == uAXON_ANIMATION_SEQUENCE_ID_NONE ) { return; }

	const u_int uUniqueID = pxCurrentItem->GetUserData();
	EditAnimTemplate( uUniqueID );
}

void Tools_AnimTemplates::EditAnimTemplate( const u_int uUniqueID )
{
	const Axon_Gamescene_AnimTemplate* pxTemplate = Axon_Gamescene_AnimTemplate_System::GetAnimTemplateByID( uUniqueID );
	if ( !pxTemplate ) { return; }

	if ( !s_xOpenAnimTemplateList.FindByGuid( uUniqueID ) )
	{
		AnimTemplateTabData xTabData;
		xTabData.m_uTemplateUniqueID = uUniqueID;
		Axon_Gamescene_AnimTemplate_Entry* pxEntry = pxTemplate->GetFirstEntry();
		while ( pxEntry )
		{
			xTabData.m_xEntryMinimisedList.Push_Back( true );
			pxEntry = pxEntry->GetNextEntry();
		}
		s_xOpenAnimTemplateList.Push_Back( xTabData );
	}

	RequestRefreshAnimTemplateOverlayWithTemplate( uUniqueID );
}

void Tools_AnimTemplates::DeleteAnimTemplate()
{
	Asura_TreeDialog_Item* pxCurrentItem = s_xAnimTemplateTree.GetSelectedItem();
	if ( !pxCurrentItem || pxCurrentItem->GetUserData() == uAXON_ANIMATION_SEQUENCE_ID_NONE ) { return; }

	const u_int uUniqueID = pxCurrentItem->GetUserData();
	const Axon_Gamescene_AnimTemplate* pxTemplate = Axon_Gamescene_AnimTemplate_System::GetAnimTemplateByID( uUniqueID );
	if ( !pxTemplate || !pxTemplate->IsLocalOnlyAndWritable() ) { return; }

	Asura_String xWarningString( "Are you sure you want to delete template '" );
	xWarningString += pxTemplate->GetName() + "'?";
	YesNo_Overlay xWarning( xWarningString.GetString() );
	if ( !xWarning.DoModal() )
	{
		return;
	}

	// Delete the template from the tree.
	s_xAnimTemplateTree.DeleteItem( pxCurrentItem );

	// If it's in the open list delete it from there and close the tab.
	// If there are no more tabs open then close the whole overlay.
	Asura_ListElt< AnimTemplateTabData >* pxElt = s_xOpenAnimTemplateList.FindByGuid( uUniqueID );
	if ( pxElt )
	{
		s_xOpenAnimTemplateList.Remove( pxElt );

		Asura_Overlay* pxOverlay = Asura_Overlay_System::GetOverlay( GetAnimTemplateOverlayID() );
		if ( pxOverlay )
		{
			Asura_Tabs_Dialog* pxTabsDialogue = static_cast< Asura_Tabs_Dialog* >( pxOverlay->GetDialog( GetAnimTemplateOverlayTabDialogueID() ) );
			if ( pxTabsDialogue )
			{
				pxTabsDialogue->DeleteTabByName( pxTemplate->GetName() );
			}
		}

		if ( s_xOpenAnimTemplateList.Size() > 0 )
		{
			RequestRefreshAnimTemplateOverlay();
		}
		else
		{
			s_pxThis->DestroyAnimTemplateOverlay();
		}
	}

	// Delete the template from the disc and from the template system.
	const Asura_String xFullFileName( pxTemplate->GetPath() + "\\" + pxTemplate->GetName() + szTEMPLATES_EXTENSION );
	Tools_FileSystem::DeleteFileByName( xFullFileName.GetString() );
	Axon_Gamescene_AnimTemplate_System::RemoveAnimTemplate( pxTemplate );
}

void Tools_AnimTemplates::AddAnimTemplateEntry( int iIndex )
{
	Axon_Gamescene_AnimTemplate* pxTemplate = GetSelectedAnimTemplate();
	if ( !pxTemplate ) { return; }

	Axon_Gamescene_AnimTemplate_Entry* pxNewEntry = NULL;
	switch ( pxTemplate->GetType() )
	{
		case AXON_ATT_STATIC:
			pxNewEntry = new Axon_Gamescene_StaticAnimTemplate_Entry();
			break;
		case AXON_ATT_LOOPINGMOVEMENT:
			pxNewEntry = new Axon_Gamescene_LoopingAnimTemplate_Entry();
			break;
		case AXON_ATT_RIGIDMOVEMENT:
			pxNewEntry = new Axon_Gamescene_RigidAnimTemplate_Entry();
			break;
		case AXON_ATT_FIDGETBEHAVIOUR:
			pxNewEntry = new Axon_Gamescene_FidgetAnimTemplate_Entry();
			break;
		default:
			Asura_Assert( false, "Tools_AnimTemplates::AddAnimTemplateEntry - bad animation template type" );
			break;
	}
	if ( !pxNewEntry ) { return; }

	PasteAnimTemplateEntry( pxTemplate, pxNewEntry, iIndex );
	SetAnimTemplateModified( pxTemplate->GetUniqueID() );
	RequestRefreshAnimTemplateOverlay();
}

void Tools_AnimTemplates::PasteAnimTemplateEntry( Axon_Gamescene_AnimTemplate* pxTemplate, Axon_Gamescene_AnimTemplate_Entry* pxCopyBuffer, const int iIndex )
{
	int iWorkingIndex = iIndex;

	// Insert a new entry into the minimised list, if it exists.
	Asura_ListElt< AnimTemplateTabData >* pxAnimTemplateTabDataElt = s_xOpenAnimTemplateList.FindByGuid( pxTemplate->GetUniqueID() );
	if ( pxAnimTemplateTabDataElt )
	{
		AnimTemplateTabData& xTabData = pxAnimTemplateTabDataElt->Data();
		Asura_LinkList_It< bool > xMinimisedListIter( xTabData.m_xEntryMinimisedList );
		while ( iWorkingIndex-- )
		{
			xMinimisedListIter.Next();
		}
		iWorkingIndex = iIndex;

		if ( iIndex > 0 && xMinimisedListIter.Done() )
		{
			// If we've reached the end of a list the iterator will return NULL and the Insert_Before function won't work as we need it to.
			// In this case we have to use Push_Back.
			xTabData.m_xEntryMinimisedList.Push_Back( false );
		}
		else
		{
			xTabData.m_xEntryMinimisedList.Insert_Before( xMinimisedListIter.GetCurrentNode(), false );
		}
	}

	// Insert a new Axon_Gamescene_AnimTemplate_Entry.
	Axon_Gamescene_AnimTemplate_Entry* pxEntry = pxTemplate->GetFirstEntry();
	Axon_Gamescene_AnimTemplate_Entry** ppxEntryPointer = &pxTemplate->m_pxFirstEntry;
	while ( iWorkingIndex-- )
	{
		ppxEntryPointer = &pxEntry->m_pxNextEntry;
		pxEntry = pxEntry->GetNextEntry();
	}

	*ppxEntryPointer = pxCopyBuffer;
	pxCopyBuffer->m_pxNextEntry = pxEntry;
}

void Tools_AnimTemplates::MoveAnimTemplateEntryUp( int iIndex )
{
	Axon_Gamescene_AnimTemplate* pxTemplate = GetSelectedAnimTemplate();
	if ( !pxTemplate ) { return; }

	s_pxThis->DestroyAnimTemplateOverlay();

	Axon_Gamescene_AnimTemplate_Entry* pxTempCopyBuffer = NULL;
	CutAnimTemplateEntry( pxTemplate, pxTempCopyBuffer, iIndex );
	PasteAnimTemplateEntry( pxTemplate, pxTempCopyBuffer, iIndex - 1 );

	SetAnimTemplateModified( pxTemplate->GetUniqueID() );
	RequestRefreshAnimTemplateOverlay();
}

void Tools_AnimTemplates::MoveAnimTemplateEntryDown( int iIndex )
{
	Axon_Gamescene_AnimTemplate* pxTemplate = GetSelectedAnimTemplate();
	if ( !pxTemplate ) { return; }

	s_pxThis->DestroyAnimTemplateOverlay();

	Axon_Gamescene_AnimTemplate_Entry* pxTempCopyBuffer = NULL;
	CutAnimTemplateEntry( pxTemplate, pxTempCopyBuffer, iIndex );
	PasteAnimTemplateEntry( pxTemplate, pxTempCopyBuffer, iIndex + 1 );

	SetAnimTemplateModified( pxTemplate->GetUniqueID() );
	RequestRefreshAnimTemplateOverlay();
}

void Tools_AnimTemplates::DeleteAnimTemplateEntry( int iIndex )
{
	Axon_Gamescene_AnimTemplate* pxTemplate = GetSelectedAnimTemplate();
	if ( !pxTemplate ) { return; }

	s_pxThis->DestroyAnimTemplateOverlay();

	Axon_Gamescene_AnimTemplate_Entry* pxTempCopyBuffer = NULL;
	CutAnimTemplateEntry( pxTemplate, pxTempCopyBuffer, iIndex );
	delete pxTempCopyBuffer;

	SetAnimTemplateModified( pxTemplate->GetUniqueID() );
	RequestRefreshAnimTemplateOverlay();
}

void Tools_AnimTemplates::CutAnimTemplateEntry( Axon_Gamescene_AnimTemplate* pxTemplate, Axon_Gamescene_AnimTemplate_Entry*& pxCopyBuffer, const int iIndex )
{
	int iWorkingIndex = iIndex;

	// Remove the entry in the minimised list, if it exists.
	Asura_ListElt< AnimTemplateTabData >* pxAnimTemplateTabDataElt = s_xOpenAnimTemplateList.FindByGuid( pxTemplate->GetUniqueID() );
	if ( pxAnimTemplateTabDataElt )
	{
		AnimTemplateTabData& xTabData = pxAnimTemplateTabDataElt->Data();
		Asura_LinkList_It< bool > xMinimisedListIter( xTabData.m_xEntryMinimisedList );
		while ( iWorkingIndex-- )
		{
			xMinimisedListIter.Next();
		}
		iWorkingIndex = iIndex;
		if ( iIndex > 0 && xMinimisedListIter.Done() )
		{
			// If we've reached the end of a list the iterator will return NULL and the Remove function won't work as we need it to.
			// In this case we have to use PopBack.
			xTabData.m_xEntryMinimisedList.PopBack();
		}
		else
		{
			xTabData.m_xEntryMinimisedList.Remove( xMinimisedListIter.GetCurrentNode() );
		}
	}

	// Remove the correct entry in the anim template.
	Axon_Gamescene_AnimTemplate_Entry* pxEntry = pxTemplate->GetFirstEntry();
	Axon_Gamescene_AnimTemplate_Entry** ppxEntryPointer = &pxTemplate->m_pxFirstEntry;
	while ( iWorkingIndex-- )
	{
		ppxEntryPointer = &pxEntry->m_pxNextEntry;
		pxEntry = pxEntry->GetNextEntry();
	}

	*ppxEntryPointer = pxEntry->m_pxNextEntry;
	pxCopyBuffer = pxEntry;
	pxEntry->m_pxNextEntry = NULL;
}

void Tools_AnimTemplates::ChooseFidgetAnimTemplate( int iIndex )
{
	Axon_Gamescene_AnimTemplate* pxTemplate = GetSelectedAnimTemplate();
	if ( !pxTemplate ) { return; }
	Asura_Assert(pxTemplate->GetType() == AXON_ATT_STATIC, "Tools_AnimTemplates::ChooseFidgetAnimTemplate - should be a static anim template");

	u_int uUniqueID;
	if ( !ChooseAnimTemplate( uUniqueID, 1 << AXON_ATT_FIDGETBEHAVIOUR ) ) { return; }

	Axon_Gamescene_AnimTemplate_Entry* pxEntry = pxTemplate->m_pxFirstEntry;
	while ( iIndex-- )
	{
		pxEntry = pxEntry->GetNextEntry();
	}

	Axon_Gamescene_StaticAnimTemplate_Entry* pxStatic = static_cast< Axon_Gamescene_StaticAnimTemplate_Entry* >( pxEntry );
	pxStatic->m_uFidgetBehaviourID = uUniqueID;
	SetAnimTemplateModified( pxTemplate->GetUniqueID() );
	RequestRefreshAnimTemplateOverlay();
}

void Tools_AnimTemplates::SetAnimTemplateEntryAnim( int iIndex )
{
	Axon_Gamescene_AnimTemplate* pxTemplate = GetSelectedAnimTemplate();
	if ( !pxTemplate ) { return; }

	Axon_Gamescene_AnimTemplate_Entry* pxEntry = pxTemplate->m_pxFirstEntry;
	while ( iIndex-- )
	{
		pxEntry = pxEntry->GetNextEntry();
	}

	Asura_String xAnimName;
	Asura_String xAnimFilename;
	Asura_Hierarchy_Anim* pxAnim = Tools_Animation_Files::ChooseAnimation( "Placeholder", NULL, &xAnimName, &xAnimFilename );
	if ( !pxAnim ) { return; }
	pxEntry->SetAnim( Asura_GetHashID( xAnimName.GetString() ), Asura_GetHashID( xAnimFilename.GetString() ) );

	SetAnimTemplateModified( pxTemplate->GetUniqueID() );
	RequestRefreshAnimTemplateOverlay();
}

void Tools_AnimTemplates::SetAnimTemplateEntryPackage( int iIndex )
{
	Axon_Gamescene_AnimTemplate* pxTemplate = GetSelectedAnimTemplate();
	if ( !pxTemplate ) { return; }

	Axon_Gamescene_AnimTemplate_Entry* pxEntry = pxTemplate->m_pxFirstEntry;
	while ( iIndex-- )
	{
		pxEntry = pxEntry->GetNextEntry();
	}

	u_int uUniqueID;
	const u_int uInitialUniqueID = pxEntry->ContainsAnim() ? Axon_GS2_AnimPackage::uUNSET_ID : pxEntry->GetAnimPackageUniqueID();
	if ( ChooseAnimPackage( uUniqueID, Axon_GS2_AnimPackage::uUNSET_ID, uInitialUniqueID, false ) )
	{
		pxEntry->SetAnimPackage( uUniqueID );

		SetAnimTemplateModified( pxTemplate->GetUniqueID() );
		RequestRefreshAnimTemplateOverlay();
	}
}

void Tools_AnimTemplates::CopySelectedTemplateEntries()
{
	Axon_Gamescene_AnimTemplate* pxTemplate = GetSelectedAnimTemplate();
	if ( !pxTemplate ) { return; }

	ClearTemplateCopyBuffer();

	s_uAnimTemplateCopyBufferType = pxTemplate->GetType();

	bool bOk = true;

	Axon_Gamescene_AnimTemplate_Entry** ppxDest = &s_pxAnimTemplateCopyBufferEntry;
	Axon_Gamescene_AnimTemplate_Entry* pxSource = pxTemplate->GetFirstEntry();
	while ( pxSource && bOk )
	{
		switch ( s_uAnimTemplateCopyBufferType )
		{
			case AXON_ATT_STATIC:			*ppxDest = new Axon_Gamescene_StaticAnimTemplate_Entry();	break;
			case AXON_ATT_LOOPINGMOVEMENT:	*ppxDest = new Axon_Gamescene_LoopingAnimTemplate_Entry();	break;
			case AXON_ATT_RIGIDMOVEMENT:	*ppxDest = new Axon_Gamescene_RigidAnimTemplate_Entry();	break;
			case AXON_ATT_FIDGETBEHAVIOUR:	*ppxDest = new Axon_Gamescene_FidgetAnimTemplate_Entry();	break;
			default: bOk = false; break;
		}

		if ( *ppxDest )
		{
			(*ppxDest)->Copy( pxSource );
			pxSource = pxSource->GetNextEntry();
			ppxDest = &((*ppxDest)->m_pxNextEntry);
		}
	}

	if ( !bOk )
	{
		ClearTemplateCopyBuffer();
	}
}

void Tools_AnimTemplates::PasteEntriesIntoSelectedTemplate()
{
	Axon_Gamescene_AnimTemplate* pxTemplate = GetSelectedAnimTemplate();
	if ( !pxTemplate ) { return; }
	if ( pxTemplate->GetType() != s_uAnimTemplateCopyBufferType ) { return; }

	bool bOk = true;

	Axon_Gamescene_AnimTemplate_Entry** ppxDest = &pxTemplate->m_pxFirstEntry;
	while ( *ppxDest )
	{
		ppxDest = &(*ppxDest)->m_pxNextEntry;
	}

	int iInsertedEntries = 0;
	Axon_Gamescene_AnimTemplate_Entry* pxSource = s_pxAnimTemplateCopyBufferEntry;
	while ( pxSource && bOk )
	{
		switch ( s_uAnimTemplateCopyBufferType )
		{
			case AXON_ATT_STATIC:			*ppxDest = new Axon_Gamescene_StaticAnimTemplate_Entry();	break;
			case AXON_ATT_LOOPINGMOVEMENT:	*ppxDest = new Axon_Gamescene_LoopingAnimTemplate_Entry();	break;
			case AXON_ATT_RIGIDMOVEMENT:	*ppxDest = new Axon_Gamescene_RigidAnimTemplate_Entry();	break;
			case AXON_ATT_FIDGETBEHAVIOUR:	*ppxDest = new Axon_Gamescene_FidgetAnimTemplate_Entry();	break;
			default: bOk = false; break;
		}

		if ( *ppxDest )
		{
			(*ppxDest)->Copy( pxSource );
			pxSource = pxSource->GetNextEntry();
			ppxDest = &((*ppxDest)->m_pxNextEntry);
			++iInsertedEntries;
		}
	}

	// Insert the requisite number of entries into the minimised list, if it exists.
	Asura_ListElt< AnimTemplateTabData >* pxAnimTemplateTabDataElt = s_xOpenAnimTemplateList.FindByGuid( pxTemplate->GetUniqueID() );
	if ( pxAnimTemplateTabDataElt )
	{
		AnimTemplateTabData& xTabData = pxAnimTemplateTabDataElt->Data();
		for ( int i = 0; i < iInsertedEntries; ++i )
		{
			xTabData.m_xEntryMinimisedList.Push_Back( true );
		}
	}

	if ( bOk )
	{
		SetAnimTemplateModified( pxTemplate->GetUniqueID() );
		RequestRefreshAnimTemplateOverlay();
	}
	else
	{
		ClearTemplateCopyBuffer();
	}
}

void Tools_AnimTemplates::ClearTemplateCopyBuffer()
{
	s_uAnimTemplateCopyBufferType = NUM_AXON_ANIM_TEMPLATE_TYPES;

	Axon_Gamescene_AnimTemplate_Entry* pxEntry = s_pxAnimTemplateCopyBufferEntry;
	while ( pxEntry )
	{
		Axon_Gamescene_AnimTemplate_Entry* pxToDelete = pxEntry;
		pxEntry = pxEntry->GetNextEntry();
		delete pxToDelete;
	}
}

void Tools_AnimTemplates::NewAnimTemplateFolder()
{
	Asura_String xNewFolderName;
	if ( !GetValidFolderName( xNewFolderName ) ) { return; }

	Asura_TreeDialog_Item* pxParentFolderItem = s_xAnimTemplateTree.GetSelectedItem();
	if ( !pxParentFolderItem ) { return; }
	if ( pxParentFolderItem->GetUserData() != uAXON_ANIMATION_SEQUENCE_ID_NONE ) { pxParentFolderItem = pxParentFolderItem->GetParent(); }
	if ( !pxParentFolderItem ) { return; }

	Asura_String xFullFolderName;
	pxParentFolderItem->ConcatenatePath( xFullFolderName );
	xFullFolderName.Prepend( szTEMPLATES_DIRECTORY );
	xFullFolderName += "\\" + xNewFolderName;
	Tools_FileSystem::EnsureDirectoryExists( xFullFolderName.GetString() );

	Asura_TreeDialog_Item* pxNewFolderItem = s_xAnimTemplateTree.InsertItem( CompareTemplateFolderItems,
																			 pxParentFolderItem,
																			 xNewFolderName.GetString(),
																			 uAXON_ANIMATION_SEQUENCE_ID_NONE );
	pxNewFolderItem->SetTreatAsFolder( true );
	s_xAnimTemplateTree.SetSelectedItem( pxNewFolderItem );
}

void Tools_AnimTemplates::DeleteAnimTemplateFolder()
{
	Asura_TreeDialog_Item* pxSelectedItem = s_xAnimTemplateTree.GetSelectedItem();
	if ( !pxSelectedItem || pxSelectedItem->GetUserData() != uAXON_ANIMATION_SEQUENCE_ID_NONE || pxSelectedItem->GetChild() ) { return; }

	Asura_String xFolderName;
	pxSelectedItem->ConcatenatePath( xFolderName );
	xFolderName.Prepend( szTEMPLATES_DIRECTORY );
	Tools_FileSystem::DeleteFolderByName( xFolderName.GetString() );
	s_xAnimTemplateTree.DeleteItem( pxSelectedItem );
}

bool Tools_AnimTemplates::GetUniqueTemplateName( Asura_String& xName )
{
	bool bNameValid = true;
	do
	{
		GetString_Overlay xOverlay( "Enter The Name Of The New Template (without spaces or extension)", xName.GetString() );
		if ( !xOverlay.DoModal() ) { return false; }
		xName = xOverlay.GetString();
		bNameValid = true;

		while ( xName.Contains( " " ) )
		{
			xName.Replace( " ", "" );
		}

		if ( xName.IsBlank() )
		{
			bNameValid = false;
			continue;
		}
		if ( xName.Contains( "\\" ) || xName.Contains( "/" ) || xName.Contains( ":" ) || xName.Contains( "*" ) ||
			xName.Contains( "?" ) || xName.Contains( "\"" ) || xName.Contains( "<" ) || xName.Contains( ">" ) || xName.Contains( "|" ) )
		{
			Message_Overlay xMessageOverlay( "Template names cannot contain any of: \\ / : * ? \" < > |" );
			xMessageOverlay.DoModal();
			bNameValid = false;
			continue;
		}
		for ( Axon_GS2_AnimTemplate_Iterator xTemplateIter; !xTemplateIter.Done(); xTemplateIter.Next() )
		{
			const Axon_Gamescene_AnimTemplate* pxTemplate = xTemplateIter.GetCurrent();
			if ( pxTemplate->GetName() == xName )
			{
				Message_Overlay xMessageOverlay( "That name is already in use." );
				xMessageOverlay.DoModal();
				bNameValid = false;
				break;
			}
		}
	} while ( !bNameValid );

	return true;
}

bool Tools_AnimTemplates::GetValidFolderName( Asura_String& xName )
{
	bool bNameValid = true;
	do
	{
		GetString_Overlay xOverlay( "Enter The Name Of The New Folder (without spaces)", xName.GetString() );
		if ( !xOverlay.DoModal() ) { return false; }
		xName = xOverlay.GetString();
		bNameValid = true;

		while ( xName.Contains( " " ) )
		{
			xName.Replace( " ", "" );
		}

		if ( xName.IsBlank() )
		{
			bNameValid = false;
			continue;
		}
		if ( xName.Contains( "\\" ) || xName.Contains( "/" ) || xName.Contains( ":" ) || xName.Contains( "*" ) ||
			xName.Contains( "?" ) || xName.Contains( "\"" ) || xName.Contains( "<" ) || xName.Contains( ">" ) || xName.Contains( "|" ) )
		{
			Message_Overlay xMessageOverlay( "Folder names cannot contain any of: \\ / : * ? \" < > |" );
			xMessageOverlay.DoModal();
			bNameValid = false;
			continue;
		}
	} while ( !bNameValid );

	return true;
}

void Tools_AnimTemplates::NewAnimPackageFolder()
{
	Asura_String xNewFolderName;
	if ( !GetValidFolderName( xNewFolderName ) ) { return; }

	Asura_TreeDialog_Item* pxParentFolderItem = s_xAnimPackageTree.GetSelectedItem();
	if ( pxParentFolderItem && pxParentFolderItem->GetUserData() != Axon_GS2_AnimPackage::uUNSET_ID ) { pxParentFolderItem = pxParentFolderItem->GetParent(); }

	Asura_String xFullFolderName;
	if ( pxParentFolderItem )
	{
		pxParentFolderItem->ConcatenatePath( xFullFolderName );
		xFullFolderName.Prepend( szPACKAGES_DIRECTORY );
		xFullFolderName += "\\";
	}
	else
	{
		xFullFolderName = szPACKAGES_DIRECTORY;
	}
	xFullFolderName += xNewFolderName;
	Tools_FileSystem::EnsureDirectoryExists( xFullFolderName.GetString() );

	Asura_TreeDialog_Item* pxNewFolderItem = s_xAnimPackageTree.InsertItem( ComparePackageFolderItems,
																			pxParentFolderItem ? pxParentFolderItem : s_xAnimPackageTree.GetRoot(),
																			xNewFolderName.GetString(),
																			Axon_GS2_AnimPackage::uUNSET_ID );
	pxNewFolderItem->SetTreatAsFolder( true );
	s_xAnimPackageTree.SetSelectedItem( pxNewFolderItem );
}

void Tools_AnimTemplates::DeleteAnimPackageFolder()
{
	Asura_TreeDialog_Item* pxSelectedItem = s_xAnimPackageTree.GetSelectedItem();
	if ( !pxSelectedItem || pxSelectedItem->GetUserData() != Axon_GS2_AnimPackage::uUNSET_ID || pxSelectedItem->GetChild() ) { return; }

	Asura_String xFolderName;
	pxSelectedItem->ConcatenatePath( xFolderName );
	xFolderName.Prepend( szPACKAGES_DIRECTORY );
	Tools_FileSystem::DeleteFolderByName( xFolderName.GetString() );
	s_xAnimPackageTree.DeleteItem( pxSelectedItem );
}

void Tools_AnimTemplates::NewAnimPackage()
{
	Asura_TreeDialog_Item* pxFolderItem = s_xAnimPackageTree.GetSelectedItem();
	if ( !pxFolderItem ) { return; }
	if ( pxFolderItem->GetUserData() != Axon_GS2_AnimPackage::uUNSET_ID ) { pxFolderItem = pxFolderItem->GetParent(); }
	if ( !pxFolderItem ) { return; }

	Asura_String xPackageName;
	if ( !GetUniquePackageName( xPackageName ) ) { return; }

	Axon_GS2_AnimPackage* pxPackage = Axon_Gamescene_AnimTemplate_System::GetInstance()->CreateAnimPackage();
	if ( !pxPackage ) { return; }
	pxPackage->AllocateUniqueID( xPackageName );
	pxPackage->SetName( xPackageName );
	// Add a new default mapping.
	pxPackage->AddAnimMapping();

	Asura_String xPath;
	pxFolderItem->ConcatenatePath( xPath );
	xPath.Prepend( szPACKAGES_DIRECTORY );
	pxPackage->SetPath( xPath );
	pxPackage->SetLocalOnlyAndWritable( true );

	Axon_Gamescene_AnimTemplate_System::AddAnimPackage( pxPackage );

	// Add the new package to the main tree and select it.
	Asura_TreeDialog_Item* pxPackageItem = s_xAnimPackageTree.InsertItem( ComparePackageItems,
																		  pxFolderItem ? pxFolderItem : s_xAnimPackageTree.GetRoot(),
																		  pxPackage->GetName().GetString(),
																		  pxPackage->GetUniqueID() );
	s_xAnimPackageTree.SetSelectedItem( pxPackageItem );

	// Add the new package to the anim package overlay.
	s_xOpenAnimPackageList.Push_Back( pxPackage->GetUniqueID() );
	RequestRefreshAnimPackageOverlay();

	SetAnimPackageModified( pxPackage->GetUniqueID() );
}

void Tools_AnimTemplates::EditAnimPackage()
{
	const Asura_TreeDialog_Item* pxCurrentItem = s_xAnimPackageTree.GetSelectedItem();
	if ( !pxCurrentItem || pxCurrentItem->GetUserData() == Axon_GS2_AnimPackage::uUNSET_ID ) { return; }

	const u_int uUniqueID = pxCurrentItem->GetUserData();
	const Axon_GS2_AnimPackage* pxPackage = Axon_Gamescene_AnimTemplate_System::GetAnimPackageByID( uUniqueID );
	if ( !pxPackage ) { return; }

	if ( !s_xOpenAnimPackageList.Find( uUniqueID ) )
	{
		s_xOpenAnimPackageList.Push_Back( uUniqueID );
	}

	RequestRefreshAnimPackageOverlayWithPackage( uUniqueID );
}

void Tools_AnimTemplates::DeleteAnimPackage()
{
	Asura_TreeDialog_Item* pxCurrentItem = s_xAnimPackageTree.GetSelectedItem();
	if ( !pxCurrentItem || pxCurrentItem->GetUserData() == Axon_GS2_AnimPackage::uUNSET_ID ) { return; }

	const u_int uUniqueID = pxCurrentItem->GetUserData();
	const Axon_GS2_AnimPackage* pxPackage = Axon_Gamescene_AnimTemplate_System::GetAnimPackageByID( uUniqueID );
	if ( !pxPackage || !pxPackage->IsLocalOnlyAndWritable() ) { return; }

	Asura_String xWarningString( "Are you sure you want to delete package '" );
	xWarningString += pxPackage->GetName() + "'?";
	YesNo_Overlay xWarning( xWarningString.GetString() );
	if ( !xWarning.DoModal() )
	{
		return;
	}

	// Delete the package from the tree.
	s_xAnimPackageTree.DeleteItem( pxCurrentItem );

	// If it's in the open list delete it from there and close the tab.
	// If there are no more tabs open then close the whole overlay.
	if ( s_xOpenAnimPackageList.Find( uUniqueID ) )
	{
		s_xOpenAnimPackageList.Remove( uUniqueID );

		Asura_Overlay* pxOverlay = Asura_Overlay_System::GetOverlay( GetAnimPackageOverlayID() );
		if ( pxOverlay )
		{
			Asura_Tabs_Dialog* pxTabsDialogue = static_cast< Asura_Tabs_Dialog* >( pxOverlay->GetDialog( GetAnimPackageOverlayTabDialogueID() ) );
			if ( pxTabsDialogue )
			{
				pxTabsDialogue->DeleteTabByName( pxPackage->GetName() );
			}
		}

		if ( s_xOpenAnimPackageList.Size() > 0 )
		{
			RequestRefreshAnimPackageOverlay();
		}
		else
		{
			s_pxThis->DestroyAnimPackageOverlay();
		}
	}

	// Delete the package from the disc and from the template system.
	const Asura_String xFullFileName( pxPackage->GetPath() + "\\" + pxPackage->GetName() + szPACKAGES_EXTENSION );
	Tools_FileSystem::DeleteFileByName( xFullFileName.GetString() );
	Axon_Gamescene_AnimTemplate_System::RemoveAnimPackage( pxPackage );
}

bool Tools_AnimTemplates::GetUniquePackageName( Asura_String& xName )
{
	bool bNameValid = true;
	do
	{
		GetString_Overlay xOverlay( "Enter The Name Of The New Package (without spaces or extension)", xName.GetString() );
		if ( !xOverlay.DoModal() ) { return false; }
		xName = xOverlay.GetString();
		bNameValid = true;

		while ( xName.Contains( " " ) )
		{
			xName.Replace( " ", "" );
		}

		if ( xName.IsBlank() )
		{
			bNameValid = false;
			continue;
		}
		if ( xName.Contains( "\\" ) || xName.Contains( "/" ) || xName.Contains( ":" ) || xName.Contains( "*" ) ||
			xName.Contains( "?" ) || xName.Contains( "\"" ) || xName.Contains( "<" ) || xName.Contains( ">" ) || xName.Contains( "|" ) )
		{
			Message_Overlay xMessageOverlay( "Package names cannot contain any of: \\ / : * ? \" < > |" );
			xMessageOverlay.DoModal();
			bNameValid = false;
			continue;
		}
		for ( Axon_GS2_AnimPackage_Iterator xPackageIter; !xPackageIter.Done(); xPackageIter.Next() )
		{
			const Axon_GS2_AnimPackage* pxPackage = xPackageIter.GetCurrent();
			if ( pxPackage->GetName() == xName )
			{
				Message_Overlay xMessageOverlay( "That name is already in use." );
				xMessageOverlay.DoModal();
				bNameValid = false;
				break;
			}
		}
	} while ( !bNameValid );

	return true;
}

Axon_Gamescene_AnimTemplate* Tools_AnimTemplates::GetSelectedAnimTemplate()
{
	Asura_Overlay* pxOverlay = Asura_Overlay_System::GetOverlay( GetAnimTemplateOverlayID() );
	if ( !pxOverlay ) { return NULL; }
	Asura_Tabs_Dialog* pxTabsDialogue = static_cast< Asura_Tabs_Dialog* >( pxOverlay->GetDialog( GetAnimTemplateOverlayTabDialogueID() ) );
	if ( !pxTabsDialogue ) { return NULL; }
	return Axon_Gamescene_AnimTemplate_System::GetAnimTemplateByName( pxTabsDialogue->GetSelectedTabName() );
}

Axon_GS2_AnimPackage* Tools_AnimTemplates::GetSelectedAnimPackage()
{
	Asura_Overlay* pxOverlay = Asura_Overlay_System::GetOverlay( GetAnimPackageOverlayID() );
	if ( !pxOverlay ) { return NULL; }
	Asura_Tabs_Dialog* pxTabsDialogue = static_cast< Asura_Tabs_Dialog* >( pxOverlay->GetDialog( GetAnimPackageOverlayTabDialogueID() ) );
	if ( !pxTabsDialogue ) { return NULL; }
	return Axon_Gamescene_AnimTemplate_System::GetAnimPackageByName( pxTabsDialogue->GetSelectedTabName() );
}

void Tools_AnimTemplates::EstablishLocalWritableAttributes()
{
	for ( Axon_GS2_AnimTemplate_Iterator xTemplateIter; !xTemplateIter.Done(); xTemplateIter.Next() )
	{
		Axon_Gamescene_AnimTemplate* pxTemplate = xTemplateIter.GetCurrent();
		const Asura_String xFullName = pxTemplate->GetPath() + "\\" + pxTemplate->GetName() + szTEMPLATES_EXTENSION;
		pxTemplate->SetLocalOnlyAndWritable( Tools_FileSystem::FileExists( xFullName.GetString() ) &&
			!Tools_FileSystem::FileExists_CheckNetworkOnly( xFullName.GetString() ) &&
			Tools_SaveAsura::CanSaveFile( xFullName.GetString(), false ) );
	}

	for ( Axon_GS2_AnimPackage_Iterator xPackageIter; !xPackageIter.Done(); xPackageIter.Next() )
	{
		Axon_GS2_AnimPackage* pxPackage = xPackageIter.GetCurrent();
		const Asura_String xFullName = pxPackage->GetPath() + "\\" + pxPackage->GetName() + szPACKAGES_EXTENSION;
		pxPackage->SetLocalOnlyAndWritable( Tools_FileSystem::FileExists( xFullName.GetString() ) &&
			!Tools_FileSystem::FileExists_CheckNetworkOnly( xFullName.GetString() ) &&
			Tools_SaveAsura::CanSaveFile( xFullName.GetString(), false ) );
	}
}

void Tools_AnimTemplates::ConvertToIndividualFiles()
{
	Tools_FileSystem::EnsureDirectoryExists( szTEMPLATES_DIRECTORY szFIDGET_DIRECTORY );
	Tools_FileSystem::EnsureDirectoryExists( szTEMPLATES_DIRECTORY szLOOPING_DIRECTORY );
	Tools_FileSystem::EnsureDirectoryExists( szTEMPLATES_DIRECTORY szRIGID_DIRECTORY );
	Tools_FileSystem::EnsureDirectoryExists( szTEMPLATES_DIRECTORY szSTATIC_DIRECTORY );

	for ( Axon_GS2_AnimTemplate_Iterator xTemplateIter; !xTemplateIter.Done(); xTemplateIter.Next() )
	{
		const Axon_Gamescene_AnimTemplate* pxTemplate = xTemplateIter.GetCurrent();

		Asura_String xFullName = szTEMPLATES_DIRECTORY;
		switch ( pxTemplate->GetType() )
		{
			case AXON_ATT_FIDGETBEHAVIOUR:	xFullName += szFIDGET_DIRECTORY;  break;
			case AXON_ATT_LOOPINGMOVEMENT:	xFullName += szLOOPING_DIRECTORY; break;
			case AXON_ATT_RIGIDMOVEMENT:	xFullName += szRIGID_DIRECTORY;   break;
			case AXON_ATT_STATIC:			xFullName += szSTATIC_DIRECTORY;  break;
			default: break;
		}

		Asura_String xTemplateName = pxTemplate->GetName();
		if ( xTemplateName.StartsWith( "FIDGET" ) )
		{
			xTemplateName.Replace( "FIDGET", "" );
		}
		else if ( xTemplateName.StartsWith( "LOOPING" ) )
		{
			xTemplateName.Replace( "LOOPING", "" );
		}
		else if ( xTemplateName.StartsWith( "LOOP" ) )
		{
			xTemplateName.Replace( "LOOP", "" );
		}
		else if ( xTemplateName.StartsWith( "STATIC" ) )
		{
			xTemplateName.Replace( "STATIC", "" );
		}
		else if ( xTemplateName.StartsWith( "STAT" ) )
		{
			xTemplateName.Replace( "STAT", "" );
		}
		else if ( xTemplateName.StartsWith( "RIGID" ) )
		{
			xTemplateName.Replace( "RIGID", "" );
		}
		xFullName += xTemplateName;
		while ( xFullName.Contains( " " ) )
		{
			xFullName.Replace( " ", "" );
		}
		xFullName += szTEMPLATES_EXTENSION;

		Axon_Chunk_AnimTemplates::SaveAnimTemplateData( xFullName.GetString(), *pxTemplate );
	}

	Tools_Timed_Message_System::AddMessage( Tools_Timed_Message_System::COLOUR_INFO, "Finished converting templates to individual files." );
}

#ifdef ASURA_USE_GAMESCENE
static const char* szGSCCat = "Conversion";
static const char* szGSCSubCat = "";

static bool ConvertStaticMessageChunk( Asura_SaveBlock& xSaveBlock, const char* szLogFileName, const Asura_Chunk_Header* pxChunk )
{
	Asura_SimpleLogfileOutput(( szLogFileName, "Converting static message chunk.\n" ));

	Asura_StaticMessage_System::Reset();

	Asura_Chunk_StaticMessages* pxSMChunk = static_cast< Asura_Chunk_StaticMessages* >( const_cast< Asura_Chunk_Header* >( pxChunk ) );
	const int iMessageSet = *(( int* )( ( u_char* )pxChunk + sizeof( Asura_Chunk_StaticMessages ) ) );

	const u_int uMemorySize = pxChunk->Size - sizeof( Asura_Chunk_StaticMessages );
	const u_char* pucChunkBuffer = ( u_char* )pxChunk + sizeof( Asura_Chunk_StaticMessages );
	Asura_Chunk_Memory_Stream xStream( uMemorySize, pucChunkBuffer );
	if ( !pxSMChunk->Process( xStream ) )
	{
		Asura_SimpleLogfileOutput(( szLogFileName, "Could not process static message chunk.\n" ));
		return false;
	}

	u_int uConvertedMessages = 0;
	u_int uTotalMessages = 0;
	for ( Tools_StaticMessageIterator xMsgIt; !xMsgIt.Done(); xMsgIt.Next() )
	{
		Asura_StaticMessage* pxMessage = xMsgIt.GetCurrent();
		++uTotalMessages;

		switch ( pxMessage->m_usMsgID )
		{
			case ASURA_MSG_AXON_GAMESCENE_LEAVE_GAMESCENE_AI:
			{
				// -> ASURA_MSG_AXON_GS2_EXIT_GAMESCENE
				Asura_Assert( pxMessage->m_usDataSize == 0, "ConvertOldGamesceneMessage - was not expecting this message to have data" );
				pxMessage->m_usMsgID = ASURA_MSG_AXON_GS2_EXIT_GAMESCENE;

				++uConvertedMessages;
				Asura_SimpleLogfileOutput(( szLogFileName, "Converted ASURA_MSG_AXON_GAMESCENE_LEAVE_GAMESCENE_AI -> ASURA_MSG_AXON_GS2_EXIT_GAMESCENE.\n" ));
				break;
			}

			case ASURA_MSG_AXON_GS2_PATHFIND_TO_NODE: // was ASURA_MSG_AXON_GAMESCENE_PATHFIND_TO_NODE
			{
				++uConvertedMessages;
				Asura_SimpleLogfileOutput(( szLogFileName, "Converted ASURA_MSG_AXON_GAMESCENE_PATHFIND_TO_NODE -> ASURA_MSG_AXON_GS2_PATHFIND_TO_NODE.\n" ));
				break;
			}

			case ASURA_MSG_AXON_GAMESCENE_NEVER_STOP_AT_NODE:
			{
				// -> ASURA_MSG_AXON_GS2_SET_NODE_PARAMS
				Asura_Assert( pxMessage->m_usDataSize == 0, "ConvertOldGamesceneMessage - was not expecting this message to have data" );
				pxMessage->m_usMsgID = ASURA_MSG_AXON_GS2_SET_NODE_PARAMS;

				pxMessage->DeallocateMessageData();
				pxMessage->AllocateMessageData( sizeof( Axon_Msg_GS2_Set_Node_Params ), true );

				Axon_Msg_GS2_Set_Node_Params xData;
				xData.Initialise();
				xData.m_bStopAtNode = false;
				xData.m_iDataValidFlags |= Axon_Msg_GS2_Set_Node_Params::STOP_AT_NODE;
				Asura_Mem::Copy( pxMessage->m_pData, &xData, pxMessage->m_usDataSize );
				
				++uConvertedMessages;
				Asura_SimpleLogfileOutput(( szLogFileName, "Converted ASURA_MSG_AXON_GAMESCENE_NEVER_STOP_AT_NODE -> ASURA_MSG_AXON_GS2_SET_NODE_PARAMS.\n" ));
				break;
			}

			case ASURA_MSG_AXON_GAMESCENE_LEAVE_NODE_IF_THERE:
			{
				// -> ASURA_MSG_AXON_GS2_LEAVE_NODE
				Asura_Assert( pxMessage->m_usDataSize == 0, "ConvertOldGamesceneMessage - was not expecting this message to have data" );
				pxMessage->m_usMsgID = ASURA_MSG_AXON_GS2_LEAVE_NODE;

				++uConvertedMessages;
				Asura_SimpleLogfileOutput(( szLogFileName, "Converted ASURA_MSG_AXON_GAMESCENE_LEAVE_NODE_IF_THERE -> ASURA_MSG_AXON_GS2_LEAVE_NODE.\n" ));
				break;
			}

			case ASURA_MSG_AXON_GS2_ANIM_SEQUENCE_DATA: // was ASURA_MSG_AXON_GAMESCENE_ANIM_SEQUENCE
			{
				++uConvertedMessages;
				Asura_SimpleLogfileOutput(( szLogFileName, "Converted ASURA_MSG_AXON_GAMESCENE_ANIM_SEQUENCE -> ASURA_MSG_AXON_GS2_ANIM_SEQUENCE_DATA.\n" ));
				break;
			}

			case ASURA_MSG_AXON_GAMESCENE_TELEPORT_TO_NODE:
			{
				// -> ASURA_MSG_AXON_GS2_TELEPORT_TO_NODE
				pxMessage->m_usMsgID = ASURA_MSG_AXON_GS2_TELEPORT_TO_NODE;

				++uConvertedMessages;
				Asura_SimpleLogfileOutput(( szLogFileName, "Converted ASURA_MSG_AXON_GAMESCENE_TELEPORT_TO_NODE -> ASURA_MSG_AXON_GS2_TELEPORT_TO_NODE.\n" ));
				break;
			}

			case ASURA_MSG_UNUSED_1: // was ASURA_MSG_AXON_GAMESCENE_ATTRACTOR_NODE_PULSE
			{
				// No GS2 equivalent for this message. Set the ID to ASURA_MSG_UNUSED_0.
				pxMessage->m_usMsgID = ASURA_MSG_UNUSED_0;
				pxMessage->DeallocateMessageData();

				++uConvertedMessages;
				Asura_SimpleLogfileOutput(( szLogFileName, "Removed ASURA_MSG_AXON_GAMESCENE_ATTRACTOR_NODE_PULSE message. There is no GS2 equivalent.\n"
					"    - sent to %u. This message must be deleted/replaced in Objects mode.\n", pxMessage->m_uSendTo ));
				break;
			}

			case ASURA_MSG_UNUSED_2: // was ASURA_MSG_AXON_GAMESCENE_ATTRACTOR_NODE_CANCEL
			{
				// No GS2 equivalent for this message. Set the ID to ASURA_MSG_UNUSED_0.
				pxMessage->m_usMsgID = ASURA_MSG_UNUSED_0;
				pxMessage->DeallocateMessageData();

				++uConvertedMessages;
				Asura_SimpleLogfileOutput(( szLogFileName, "Removed ASURA_MSG_AXON_GAMESCENE_ATTRACTOR_NODE_CANCEL message. There is no GS2 equivalent.\n"
					"    - sent to %u. This message must be deleted/replaced in Objects mode.\n", pxMessage->m_uSendTo ));
				break;
			}

			case ASURA_MSG_UNUSED_3: // was ASURA_MSG_AXON_GAMESCENE_ATTRACTOR_NODE_ARRIVED
			{
				// No GS2 equivalent for this message. Set the ID to ASURA_MSG_UNUSED_0.
				pxMessage->m_usMsgID = ASURA_MSG_UNUSED_0;
				pxMessage->DeallocateMessageData();

				++uConvertedMessages;
				Asura_SimpleLogfileOutput(( szLogFileName, "Removed ASURA_MSG_AXON_GAMESCENE_ATTRACTOR_NODE_ARRIVED message. There is no GS2 equivalent.\n"
					"    - sent to %u. This message must be deleted/replaced in Objects mode.\n", pxMessage->m_uSendTo ));
				break;
			}

			case ASURA_MSG_AXON_GS2_DONT_STOP_AT_NODE: // was ASURA_MSG_AXON_ENABLE_LINK
			{
				// No GS2 equivalent for this message. Set the ID to ASURA_MSG_UNUSED_0.
				pxMessage->m_usMsgID = ASURA_MSG_UNUSED_0;
				pxMessage->DeallocateMessageData();

				++uConvertedMessages;
				Asura_SimpleLogfileOutput(( szLogFileName, "Removed ASURA_MSG_AXON_ENABLE_LINK message. There is no GS2 equivalent.\n"
					"    - sent to %u. This message must be deleted/replaced in Objects mode.\n", pxMessage->m_uSendTo ));
				break;
			}

			case ASURA_MSG_AXON_GS2_SET_NODE_PARAMS: // was ASURA_MSG_AXON_DISABLE_LINK
			{
				// No GS2 equivalent for this message. Set the ID to ASURA_MSG_UNUSED_0.
				pxMessage->m_usMsgID = ASURA_MSG_UNUSED_0;
				pxMessage->DeallocateMessageData();

				++uConvertedMessages;
				Asura_SimpleLogfileOutput(( szLogFileName, "Removed ASURA_MSG_AXON_DISABLE_LINK message. There is no GS2 equivalent.\n"
					"    - sent to %u. This message must be deleted/replaced in Objects mode.\n", pxMessage->m_uSendTo ));
				break;
			}

			case ASURA_MSG_UNUSED_0: // was ASURA_MSG_AXON_GAMESCENE_FORCE_ACTOR_EXIT
			{
				// -> ASURA_MSG_AXON_GS2_LEAVE_NODE
				Asura_Assert( pxMessage->m_usDataSize == 0, "ConvertOldGamesceneMessage - was not expecting this message to have data" );
				pxMessage->m_usMsgID = ASURA_MSG_AXON_GS2_LEAVE_NODE;

				++uConvertedMessages;
				Asura_SimpleLogfileOutput(( szLogFileName, "Converted ASURA_MSG_AXON_GAMESCENE_FORCE_ACTOR_EXIT -> ASURA_MSG_AXON_GS2_LEAVE_NODE.\n" ));
				break;
			}

			case ASURA_MSG_AXON_GAMESCENE_LEAVE_NODE:
			{
				// -> ASURA_MSG_AXON_GS2_LEAVE_NODE
				Asura_Assert( pxMessage->m_usDataSize == 0, "ConvertOldGamesceneMessage - was not expecting this message to have data" );
				pxMessage->m_usMsgID = ASURA_MSG_AXON_GS2_LEAVE_NODE;

				++uConvertedMessages;
				Asura_SimpleLogfileOutput(( szLogFileName, "Converted ASURA_MSG_AXON_GAMESCENE_LEAVE_NODE -> ASURA_MSG_AXON_GS2_LEAVE_NODE.\n" ));
				break;
			}

			case ASURA_MSG_AXON_GAMESCENE_ATTRACT_TO_NODE:
			{
				// No GS2 equivalent for this message. Set the ID to ASURA_MSG_UNUSED_0.
				pxMessage->m_usMsgID = ASURA_MSG_UNUSED_0;
				pxMessage->DeallocateMessageData();

				++uConvertedMessages;
				Asura_SimpleLogfileOutput(( szLogFileName, "Removed ASURA_MSG_AXON_GAMESCENE_ATTRACT_TO_NODE message. There is no GS2 equivalent.\n"
					"    - sent to %u. This message must be deleted/replaced in Objects mode.\n", pxMessage->m_uSendTo ));
				break;
			}

			case ASURA_MSG_AXON_GAMESCENE_ALWAYS_STOP_AT_NODE:
			{
				// -> ASURA_MSG_AXON_GS2_SET_NODE_PARAMS
				// Have to set the params to stop at node, but we don't know if there will be valid anims or even the right behaviour set on this node.
				pxMessage->m_usMsgID = ASURA_MSG_AXON_GS2_SET_NODE_PARAMS;

				pxMessage->DeallocateMessageData();
				pxMessage->AllocateMessageData( sizeof( Axon_Msg_GS2_Set_Node_Params ), true );

				Axon_Msg_GS2_Set_Node_Params xData;
				xData.Initialise();
				xData.m_bStopAtNode = true;
				xData.m_iDataValidFlags |= Axon_Msg_GS2_Set_Node_Params::STOP_AT_NODE;
				Asura_Mem::Copy( pxMessage->m_pData, &xData, pxMessage->m_usDataSize );

				++uConvertedMessages;
				Asura_SimpleLogfileOutput(( szLogFileName,
					"Converted ASURA_MSG_AXON_GAMESCENE_ALWAYS_STOP_AT_NODE -> ASURA_MSG_AXON_GS2_SET_NODE_PARAMS.\n"
					"    - sent to %u. This message needs other data set on it (anim set, etc).\n" , pxMessage->m_uSendTo ));
				break;
			}

			case ASURA_MSG_AXON_GAMESCENE_ACTOR_ATTRACT_TO_NODE:
			{
				// No GS2 equivalent for this message. Set the ID to ASURA_MSG_UNUSED_0.
				pxMessage->m_usMsgID = ASURA_MSG_UNUSED_0;
				pxMessage->DeallocateMessageData();

				++uConvertedMessages;
				Asura_SimpleLogfileOutput(( szLogFileName, "Removed ASURA_MSG_AXON_GAMESCENE_ACTOR_ATTRACT_TO_NODE message. There is no GS2 equivalent.\n"
					"    - sent to %u. This message must be deleted/replaced in Objects mode.\n", pxMessage->m_uSendTo ));
				break;
			}

			case ASURA_MSG_AXON_GAMESCENE_SET_ATTRACTOR_FLAGS:
			{
				// No GS2 equivalent for this message. Set the ID to ASURA_MSG_UNUSED_0.
				pxMessage->m_usMsgID = ASURA_MSG_UNUSED_0;
				pxMessage->DeallocateMessageData();

				++uConvertedMessages;
				Asura_SimpleLogfileOutput(( szLogFileName, "Removed ASURA_MSG_AXON_GAMESCENE_SET_ATTRACTOR_FLAGS message. There is no GS2 equivalent.\n"
					"    - sent to %u. This message must be deleted/replaced in Objects mode.\n", pxMessage->m_uSendTo ));
				break;
			}

			case ASURA_MSG_AXON_GS2_TAKE_LINK: // was ASURA_MSG_AXON_GAMESCENE_TAKE_LINK
			{
				++uConvertedMessages;
				Asura_SimpleLogfileOutput(( szLogFileName, "Converted ASURA_MSG_AXON_GAMESCENE_TAKE_LINK -> ASURA_MSG_AXON_GS2_TAKE_LINK.\n" ));
				break;
			}
		}
	}

	Asura_SimpleLogfileOutput(( szLogFileName, "Converted %u out of %u messages.\n", uConvertedMessages, uTotalMessages ));

	Asura_Chunk_StaticMessages::Create( xSaveBlock, iMessageSet );
	Asura_StaticMessage_System::Reset();

	return true;
}

static void ConvertNode( const char* szLogFileName, const BF3_ServerNode_GamesceneNode* pxGamesceneNode, BF3_ServerNode_GS2Node* pxGS2Node )
{
	pxGS2Node->SetBehaviourSet( AXON_BEHAVIOUR_TYPE_GS2_NODE );
	AxonBF3_BehaviourParams_GS2Node* pxBehaviourParams = static_cast< AxonBF3_BehaviourParams_GS2Node* >( pxGS2Node->GetBehaviourParams() );

	// Asura_Entity
	pxGS2Node->SetEntityActive( pxGamesceneNode->IsEntityActive() );
	pxGS2Node->SetBlueprintTypeHashID( pxGamesceneNode->GetBlueprintTypeHashID() );
	pxGS2Node->SetBlueprintHashID( pxGamesceneNode->GetBlueprintHashID() );

	// BF3_Entity_Server
	pxGS2Node->SetZonesWithoutUpdate( pxGamesceneNode->GetZones( true ) );
	pxGS2Node->GetGametypeData() = pxGamesceneNode->GetGametypeData();

	// Asura_Entity_Server
	for ( int i = 0; i < pxGamesceneNode->GetNumEvents(); ++i )
	{
		if ( pxGamesceneNode->GetEvents()[ i ].m_usEventID == BF3_ENTITY_EVENT_ONDEPARTURE )
		{
			pxGS2Node->SetMessageBlockForEvent( ASURA_ENTITY_EVENT_ONDEPARTEDFROM, pxGamesceneNode->GetEvents()[ i ].m_usMessageBlockID );
		}
		else
		{
			pxGS2Node->SetMessageBlockForEvent( pxGamesceneNode->GetEvents()[ i ].m_usEventID, pxGamesceneNode->GetEvents()[ i ].m_usMessageBlockID );
		}
	}

	// Asura_ServerEntity_Node
	for ( u_int uSpline = 0; uSpline < pxGamesceneNode->GetNumberLinks(); ++uSpline )
	{
		pxGS2Node->AddNewSpline( pxGamesceneNode->GetLink( uSpline ) );
	}
	pxGS2Node->SetIsSmoothOverNode( pxGamesceneNode->IsSmoothOverNode() );
	pxGS2Node->SetPosition( pxGamesceneNode->GetPosition() );
	pxGS2Node->SetToolsObjectColour( pxGamesceneNode->GetToolsObjectColour() );

	// Asura_ServerNode_OrientedNode
	Asura_Quat xOrientQ;
	pxGamesceneNode->GetOrientation( xOrientQ );
	pxGS2Node->SetOrientation( xOrientQ );

	// Asura_Gamescene_Node_Base
	pxGS2Node->SetCanCrouch( pxGamesceneNode->CanCrouch() );
	pxGS2Node->SetCanStand( pxGamesceneNode->CanStand() );
	pxGS2Node->SetShouldTestForCollisions( !pxGamesceneNode->DisableActorCollisions() );
	pxGS2Node->SetShouldIgnoreCollisions( pxGamesceneNode->DontTestDynamicsPosition() );
	pxGS2Node->SetShouldFollowSurface( pxGamesceneNode->IsFollowGround( true ) );
	pxBehaviourParams->m_uAnimSet = pxGamesceneNode->GetAnimationSequenceID();
	switch ( pxGamesceneNode->GetExitConditions() )
	{
		case ASURA_GAMESCENE_EXIT_NONE: pxBehaviourParams->m_iExitCondition = AXON_GS2_NEC_NONE; break;
		case ASURA_GAMESCENE_EXIT_ON_ALERTED: pxBehaviourParams->m_iExitCondition = AXONBF3_GS2_NEC_ON_ALERTED; break;
		case ASURA_GAMESCENE_EXIT_ON_ANIMATION_SEQUENCE_FINISHED: pxBehaviourParams->m_iExitCondition = AXON_GS2_NEC_ANIMS_FINISHED; break;
		case ASURA_GAMESCENE_EXIT_ON_DISTANCE_FROM_ACTOR:
		{
			pxBehaviourParams->m_iExitCondition = AXONBF3_GS2_NEC_ON_DISTANCE_FROM_ACTOR;
			pxBehaviourParams->m_uNECGuid = pxGamesceneNode->GetRangeActorGuid();
			pxBehaviourParams->m_fNECDistance = pxGamesceneNode->GetRangeFromActor();
			break;
		}
		case ASURA_GAMESCENE_EXIT_ON_TIME_OUT:
		{
			pxBehaviourParams->m_iExitCondition = AXON_GS2_NEC_TIMEOUT;
			pxBehaviourParams->m_fMinExitWaitTime = pxGamesceneNode->GetMinExitWaitTime();
			pxBehaviourParams->m_fMaxExitWaitTime = pxGamesceneNode->GetMaxExitWaitTime();
			break;
		}
		case ASURA_GAMESCENE_EXIT_ON_SPOT_ENEMY: pxBehaviourParams->m_iExitCondition = AXONBF3_GS2_NEC_ON_SPOT_ENEMY; break;
		case ASURA_GAMESCENE_EXIT_ON_SPOT_ALLY: pxBehaviourParams->m_iExitCondition = AXONBF3_GS2_NEC_ON_SPOT_ALLY; break;
		case ASURA_GAMESCENE_EXIT_ON_SPOT_SPECIFIC_GUID:
		{
			Asura_SimpleLogfileOutput(( szLogFileName, "    - node is using 'on spot specific guid' exit condition, which will be removed.\n" ));
			break;
		}
		case ASURA_GAMESCENE_EXIT_ON_TAKE_DAMAGE: pxBehaviourParams->m_iExitCondition = AXONBF3_GS2_NEC_ON_TAKE_DAMAGE; break;
		default: Asura_Assert( false, "ConvertOldGamesceneEntities - bad exit condition" ); break;
	}

	// BF3_Gamescene_Node_Base
	if ( pxGamesceneNode->GetBeginCombatCondition() == BF3_GAMESCENE_BEGIN_COMBAT_ON_SPOT_SPECIFIC_ENEMY_GUID )
	{
		Asura_SimpleLogfileOutput(( szLogFileName, "    - node is using 'on spot specific guid' begin combat condition, which will be removed.\n" ));
	}
	else
	{
		pxGS2Node->SetBeginCombatCondition( pxGamesceneNode->GetBeginCombatCondition() );
	}

	// Asura_ServerNode_GamesceneNode
	pxBehaviourParams->m_bStopHere = pxGamesceneNode->GetStopHere();

	// BF3_ServerNode_GamesceneNode
	pxGS2Node->SetLookAtTarget( pxGamesceneNode->GetLookAtGuid() );
}

static void ConvertAttractor( const char* szLogFileName, const BF3_ServerNode_GamesceneAttractor* pxGamesceneAttr, BF3_ServerNode_GS2Attractor* pxGS2Attr )
{
	ConvertNode( szLogFileName, pxGamesceneAttr, pxGS2Attr );

	pxGS2Attr->SetEnRouteBehaviourSet( AXON_BEHAVIOUR_TYPE_GS2_ATTRACTOR_EN_ROUTE );

	// Asura_ServerNode_GamesceneAttractor.
	switch ( pxGamesceneAttr->GetTargetType() )
	{
		case ASURA_ATTRACTOR_TARGET_TYPE_GUID:
		{
			pxGS2Attr->SetAttractType( AXON_GS2_AAT_SPECIFIC_GUID );
			pxGS2Attr->SetSpecificGuid( pxGamesceneAttr->GetTargetGuid() );
			break;
		}

		case ASURA_ATTRACTOR_TARGET_TYPE_ANYONE:	pxGS2Attr->SetAttractType( AXON_GS2_AAT_ANYTHING ); break;
		case BF3_ATTRACTOR_TARGET_TYPE_GOOD:		pxGS2Attr->SetAttractType( AXONBF3_GS2_AAT_GOOD );  break;
		case BF3_ATTRACTOR_TARGET_TYPE_EVIL:		pxGS2Attr->SetAttractType( AXONBF3_GS2_AAT_EVIL );  break;

		default: Asura_Assert( false, "ConvertOldGamesceneEntities - bad target type" ); break;
	}
	pxGS2Attr->SetAttractRange( pxGamesceneAttr->GetTargetRange() );
}

static void ConvertSpline( const char* szLogFileName, BF3_ServerNode_GamesceneSpline* pxGamesceneSpline, BF3_ServerNode_GS2Spline* pxGS2Spline )
{
	pxGS2Spline->SetBehaviourSet( AXON_BEHAVIOUR_TYPE_GS2_SPLINE );
	Axon_BehaviourParams_GS2Spline* pxBehaviourParams = static_cast< Axon_BehaviourParams_GS2Spline* >( pxGS2Spline->GetBehaviourParams() );

	// Asura_Entity
	pxGS2Spline->SetEntityActive( pxGamesceneSpline->IsEntityActive() );
	pxGS2Spline->SetBlueprintTypeHashID( pxGamesceneSpline->GetBlueprintTypeHashID() );
	pxGS2Spline->SetBlueprintHashID( pxGamesceneSpline->GetBlueprintHashID() );

	// BF3_Entity_Server
	pxGS2Spline->SetZonesWithoutUpdate( pxGamesceneSpline->GetZones( true ) );
	pxGS2Spline->GetGametypeData() = pxGamesceneSpline->GetGametypeData();

	// Asura_Entity_Server
	for ( int i = 0; i < pxGamesceneSpline->GetNumEvents(); ++i )
	{
		if ( pxGamesceneSpline->GetEvents()[ i ].m_usEventID == BF3_ENTITY_EVENT_ONDEPARTURE )
		{
			pxGS2Spline->SetMessageBlockForEvent( ASURA_ENTITY_EVENT_ONDEPARTEDFROM, pxGamesceneSpline->GetEvents()[ i ].m_usMessageBlockID );
		}
		else
		{
			pxGS2Spline->SetMessageBlockForEvent( pxGamesceneSpline->GetEvents()[ i ].m_usEventID, pxGamesceneSpline->GetEvents()[ i ].m_usMessageBlockID );
		}
	}

	// Asura_ServerEntity_Node.
	pxGS2Spline->SetPosition( pxGamesceneSpline->GetPosition() );
	pxGS2Spline->SetToolsObjectColour( pxGamesceneSpline->GetToolsObjectColour() );

	// Asura_Oriented_Spline.
	pxGS2Spline->CopyFrom( pxGamesceneSpline );

	// Asura_ServerNode_Spline.
	pxGS2Spline->SetSourceGuid( pxGamesceneSpline->GetSourceGuid() );
	pxGS2Spline->SetTargetGuid( pxGamesceneSpline->GetTargetGuid() );
	for ( u_int uSplineEvent = 0; uSplineEvent < pxGamesceneSpline->GetNumSplineEvents(); ++uSplineEvent )
	{
		pxGS2Spline->AddSplineEvent();
		pxGS2Spline->GetSplineEventDistance( uSplineEvent ) = pxGamesceneSpline->GetSplineEventDistance( uSplineEvent );
		pxGS2Spline->GetSplineEventMessageBlock( uSplineEvent ) = pxGamesceneSpline->GetSplineEventMessageBlock( uSplineEvent );
		pxGS2Spline->GetSplineEventName( uSplineEvent ) = pxGamesceneSpline->GetSplineEventName( uSplineEvent );
	}

	// Asura_Gamescene_Node_Base.
	pxGS2Spline->SetCanCrouch( pxGamesceneSpline->CanCrouch() );
	pxGS2Spline->SetCanStand( pxGamesceneSpline->CanStand() );
	pxGS2Spline->SetShouldTestForCollisions( !pxGamesceneSpline->DisableActorCollisions() );
	pxGS2Spline->SetShouldIgnoreCollisions( pxGamesceneSpline->DontTestDynamicsPosition() );
	pxGS2Spline->SetShouldFollowSurface( pxGamesceneSpline->IsFollowGround( true ) );
	pxBehaviourParams->m_uAnimSet = pxGamesceneSpline->GetAnimationSequenceID();

	// Asura_ServerNode_GamesceneSpline.
	pxGS2Spline->SetUsePointOrientation( pxGamesceneSpline->ShouldTakeOrientationFromPoints() );
	if ( pxGamesceneSpline->ShouldUsePathFinding() )
	{
		pxBehaviourParams->m_iTraverseMode = AXON_GS2_STM_FREE_MOVEMENT;
	}
	else if ( pxGamesceneSpline->ShouldTakeSpeedFromAnimation() )
	{
		Axon_Gamescene_AnimTemplate* pxAnimationTemplate = NULL;
		const u_int uIndex = Axon_Gamescene_AnimTemplate_System::GetIndexFromUniqueID( pxGamesceneSpline->GetAnimationSequenceID() );
		if ( uIndex != uAXON_ANIMATION_SEQUENCE_INDEX_NONE )
		{
			pxAnimationTemplate = Axon_Gamescene_AnimTemplate_System::GetAnimTemplateByIndex( uIndex );
		}
		if ( pxAnimationTemplate && pxAnimationTemplate->m_uTemplateType == AXON_ATT_RIGIDMOVEMENT )
		{
			pxBehaviourParams->m_iTraverseMode = AXON_GS2_STM_ANIM_MOVEMENT;
		}
		else
		{
			pxBehaviourParams->m_iTraverseMode = AXON_GS2_STM_SPLINE_PATH_ANIM_SPEED;
		}
	}
	else
	{
		pxBehaviourParams->m_iTraverseMode = AXON_GS2_STM_SPLINE_PATH_SPLINE_SPEED;
	}
	pxGS2Spline->SetDistanceVsTimeGraph( pxGamesceneSpline->GetDistanceVsTimeGraph() );
	pxGS2Spline->SetOverrideDurationFlag( pxGamesceneSpline->GetOverrideDurationFlag() );
	*pxGS2Spline->GetSpeedOverTimeGraph() = *pxGamesceneSpline->GetSpeedOverTimeGraph();
	pxGS2Spline->SetTotalTimeRequired( pxGamesceneSpline->GetTotalTimeRequired() );
	pxGS2Spline->SetOverrideDurationValue( pxGamesceneSpline->GetOverrideDurationValue() );
	if ( pxGamesceneSpline->GetPauseCondition() != 0 )
	{
		Asura_SimpleLogfileOutput(( szLogFileName, "    - spline has a pause condition, which will be removed.\n" ));
	}

	// BF3_Gamescene_Node_Base.
	if ( pxGamesceneSpline->GetBeginCombatCondition() == BF3_GAMESCENE_BEGIN_COMBAT_ON_SPOT_SPECIFIC_ENEMY_GUID )
	{
		Asura_SimpleLogfileOutput(( szLogFileName, "    - spline is using 'on spot specific guid' begin combat condition, which will be removed.\n" ));
	}
	else
	{
		pxGS2Spline->SetBeginCombatCondition( pxGamesceneSpline->GetBeginCombatCondition() );
	}

	pxGS2Spline->OnChanged();
}

static bool ConvertEntityChunk( Asura_SaveBlock& xSaveBlock, const char* szLogFileName, const Asura_Chunk_Header* pxChunk )
{
	Asura_Chunk_Entity* pxEChunk = static_cast< Asura_Chunk_Entity* >( const_cast< Asura_Chunk_Header* >( pxChunk ) );

	switch ( pxEChunk->Classification )
	{
		case BF3_ENTITYCLASS_GAMESCENEATTRACTOR:
		{
			Asura_SimpleLogfileOutput(( szLogFileName, "Converting gamescene attractor with guid %u.\n", pxEChunk->Guid ));
			Asura_SimpleLogfileOutput(( szLogFileName, "    - attractors may need an anim template set in their en-route behaviour params.\n" ));

			const u_char* pucChunkBuffer = ( u_char* )pxChunk + sizeof( Asura_Chunk_Entity );
			Asura_Chunk_Memory_Stream xStreamIn( pxChunk->Size - sizeof( Asura_Chunk_Entity ), pucChunkBuffer );

			BF3_ServerNode_GamesceneAttractor* pxGamesceneAttr = new BF3_ServerNode_GamesceneAttractor( ASURA_GUID_TOOLSCREATEDENTITY_END );
			pxGamesceneAttr->ReadFromChunkStream( xStreamIn );
			BF3_ServerNode_GS2Attractor* pxGS2Attr = new BF3_ServerNode_GS2Attractor( pxEChunk->Guid );
			ConvertAttractor( szLogFileName, pxGamesceneAttr, pxGS2Attr );
			delete pxGamesceneAttr;

			Asura_Chunk_Entity::Create( pxGS2Attr, xSaveBlock );
			delete pxGS2Attr;

			break;
		}

		case BF3_ENTITYCLASS_GAMESCENENODE:
		{
			Asura_SimpleLogfileOutput(( szLogFileName, "Converting gamescene node with guid %u.\n", pxEChunk->Guid ));

			const u_char* pucChunkBuffer = ( u_char* )pxChunk + sizeof( Asura_Chunk_Entity );
			Asura_Chunk_Memory_Stream xStreamIn( pxChunk->Size - sizeof( Asura_Chunk_Entity ), pucChunkBuffer );

			BF3_ServerNode_GamesceneNode* pxGamesceneNode = new BF3_ServerNode_GamesceneNode( ASURA_GUID_TOOLSCREATEDENTITY_END );
			pxGamesceneNode->ReadFromChunkStream( xStreamIn );
			BF3_ServerNode_GS2Node* pxGS2Node = new BF3_ServerNode_GS2Node( pxEChunk->Guid );
			ConvertNode( szLogFileName, pxGamesceneNode, pxGS2Node );
			delete pxGamesceneNode;

			Asura_Chunk_Entity::Create( pxGS2Node, xSaveBlock );
			delete pxGS2Node;

			break;
		}

		case BF3_ENTITYCLASS_GAMESCENESPLINE:
		{
			Asura_SimpleLogfileOutput(( szLogFileName, "Converting gamescene spline with guid %u.\n", pxEChunk->Guid ));

			const u_char* pucChunkBuffer = ( u_char* )pxChunk + sizeof( Asura_Chunk_Entity );
			Asura_Chunk_Memory_Stream xStreamIn( pxChunk->Size - sizeof( Asura_Chunk_Entity ), pucChunkBuffer );

			BF3_ServerNode_GamesceneSpline* pxGamesceneSpline = new BF3_ServerNode_GamesceneSpline( ASURA_GUID_TOOLSCREATEDENTITY_END );
			pxGamesceneSpline->ReadFromChunkStream( xStreamIn );
			BF3_ServerNode_GS2Spline* pxGS2Spline = new BF3_ServerNode_GS2Spline( pxEChunk->Guid );
			ConvertSpline( szLogFileName, pxGamesceneSpline, pxGS2Spline );
			delete pxGamesceneSpline;

			Asura_Chunk_Entity::Create( pxGS2Spline, xSaveBlock );
			delete pxGS2Spline;

			break;
		}

		default:
		{
			// Just copy the chunk straight over.
			xSaveBlock.AddData( pxChunk, pxChunk->Size );
			break;
		}
	}

	return true;
}

static bool OutputOriginalChunk( Asura_SaveBlock& xSaveBlock, const char* szLogFileName, const Asura_Chunk_Header* pxChunk )
{
	void* pBuffer = xSaveBlock.GetAccessPointer( pxChunk->Size );
	memcpy( pBuffer, pxChunk, pxChunk->Size );
	return true;
}

static bool ConvertFileData( const Asura_String& xFileName, Tools_AsuraFileChunkLister& xChunkLister )
{
	Asura_String xPath;
	Asura_String xName;
	Asura_String xExt;
	xFileName.SplitFilename( &xPath, &xName, &xExt );
	xPath.TidyFilename();

	Asura_String xLogFileName = xPath;
	xLogFileName += "\\GamesceneConversion_";
	xLogFileName += xName + "_";
	xLogFileName += xExt + ".txt";

	Asura_SimpleLogfileClear( xLogFileName.GetString() );
	Asura_SimpleLogfileOutput(( xLogFileName.GetString(), "Starting gamescene -> GS2 conversion.\n\n" ));

	Asura_SaveBlock xSaveBlock;

	// Add a chunk to say we've converted gamescene to GS2.
	BF3_Chunk_ConvertedGamescene::Create( &xSaveBlock );

	const List< const Asura_Chunk_Header* >* pxChunkList = xChunkLister.GetChunkList();

	bool bOk = true;
	u_int uNumChunks = 0;
	u_int uNumConverted = 0;
	for ( CLIF< const Asura_Chunk_Header* > xChunkLIF( pxChunkList ); !xChunkLIF.done() && bOk; xChunkLIF.next() )
	{
		const Asura_Chunk_Header* pxChunkHeader = xChunkLIF();
		++uNumChunks;

		// Throw the type away, as we have both asura and project chunk cases.
		switch ( (int)pxChunkHeader->ID )
		{
			case BF3_CHUNK_CONVERTED_GAMESCENE:
			{
				// We should not come across one of these - it means this file has already been converted.
				Asura_SimpleLogfileOutput(( xLogFileName.GetString(), "This file has already been converted. Conversion must be done once only.\n" ));
				bOk = false;
				break;
			}

			case ASURA_CHUNK_STATICMESSAGES:
			{
				bOk = ConvertStaticMessageChunk( xSaveBlock, xLogFileName.GetString(), pxChunkHeader );
				++uNumConverted;
				break;
			}

			case ASURA_CHUNK_ENTITY:
			{
				bOk = ConvertEntityChunk( xSaveBlock, xLogFileName.GetString(), pxChunkHeader );
				++uNumConverted;
				break;
			}

			default:
			{
				bOk = OutputOriginalChunk( xSaveBlock, xLogFileName.GetString(), pxChunkHeader );
				break;
			}
		}
	}
	xSaveBlock.AddAsuraTerminator();

	Asura_SimpleLogfileOutput(( xLogFileName.GetString(), "Converted %u out of %u chunks.\n", uNumConverted, uNumChunks ));

	if ( bOk )
	{
		Asura_SimpleLogfileOutput(( xLogFileName.GetString(), "\nFinished gamescene -> GS2 conversion.\n" ));

		// Delete the original file.
		if ( DeleteFile( xFileName.GetString() ) )
		{
			// Save the new file.
			if ( xSaveBlock.SaveFile( xFileName.GetString(), false ) )
			{
				Asura_SimpleLogfileOutput(( xLogFileName.GetString(), "Saved new file %s.", xFileName.GetString() ));
				Tools_MessageLog::AddMessage( szGSCCat, szGSCSubCat, "Saved new file %s.", xFileName.GetString() );
			}
			else
			{
				Asura_SimpleLogfileOutput(( xLogFileName.GetString(), "Failed to save new file %s.", xFileName.GetString() ));
				Tools_MessageLog::AddMessage( szGSCCat, szGSCSubCat, "Failed to save new file %s.", xFileName.GetString() );
				bOk = false;
			}
		}
		else
		{
			Asura_SimpleLogfileOutput(( xLogFileName.GetString(), "Could not delete original file %s.", xFileName.GetString() ));
			Tools_MessageLog::AddMessage( szGSCCat, szGSCSubCat, "Could not delete original file %s.", xFileName.GetString() );
			bOk = false;
		}
	}

	if ( !bOk )
	{
		Asura_SimpleLogfileOutput(( xLogFileName.GetString(), "Gamescene -> GS2 conversion finished in error.\n" ));
	}

	return bOk;
}

void Tools_Mode_AnimTemplates::FindRelevantFiles()
{
	Tools_ProgressIndicator xPI( "Finding files for gamescene -> GS2 conversion." );
	Tools_MessageLog::DeleteMessages( szGSCCat, szGSCSubCat );
	Tools_MessageLog::AddMessage( szGSCCat, szGSCSubCat, "Finding files for gamescene -> GS2 conversion." );

	xPI.AddMessage( "Enumerating all files." );
	Tools_MessageLog::AddMessage( szGSCCat, szGSCSubCat, "Enumerating all files." );

	List< Asura_String > xAllFiles;
	Tools_FileSystem::FindFilesLocalAndNetwork( "*.*", xAllFiles, ( TOOLS_FILEFIND_RECURSIVE | TOOLS_FILEFIND_IGNOREBACKUPDIR ) );

	const int iNumFiles = xAllFiles.size();
	xPI.AddMessage( "Found %d files.", iNumFiles );
	Tools_MessageLog::AddMessage( szGSCCat, szGSCSubCat, "Found %d files.", iNumFiles );

	xPI.AddMessage( "Checking files for entity or static message chunks." );
	Tools_MessageLog::AddMessage( szGSCCat, szGSCSubCat, "Checking files for entity or static message chunks." );
	List< Asura_String > xRelevantFiles;
	int iFile = 0;
	for ( LIF< Asura_String > xFilenameLIF( &xAllFiles ); !xFilenameLIF.done(); xFilenameLIF.next() )
	{
		xPI.AddMessage( "Checking file %s.", xFilenameLIF() );
		xPI.Update( iFile++, iNumFiles );

		Tools_AsuraFileChunkLister xChunkLister;
		if ( !xChunkLister.LoadAsuraFile( xFilenameLIF().GetString(), true, true ) )
		{
			Tools_MessageLog::AddMessage( szGSCCat, szGSCSubCat, "Cannot open file %s.", xFilenameLIF() );
			continue;
		}

		const List< const Asura_Chunk_Header* >* pxChunkList = xChunkLister.GetChunkList();
		bool bDone = false;
		for ( CLIF< const Asura_Chunk_Header* > xChunkLIF( pxChunkList ); !xChunkLIF.done() && !bDone; xChunkLIF.next() )
		{
			switch ( xChunkLIF()->ID )
			{
				case ASURA_CHUNK_STATICMESSAGES:
				case ASURA_CHUNK_ENTITY:
				{
					xRelevantFiles.add_entry( xFilenameLIF() );
					bDone = true;
					break;
				}

				default: break;
			}
		}
	}

	Tools_MessageLog::AddMessage( szGSCCat, szGSCSubCat, "Found %d relevant files:", xRelevantFiles.size() );
	for ( LIF< Asura_String > xFilenameLIF( &xRelevantFiles ); !xFilenameLIF.done(); xFilenameLIF.next() )
	{
		Tools_MessageLog::AddMessage( szGSCCat, szGSCSubCat, "%s", xFilenameLIF() );
	}

	Tools_MessageLog::AddMessage( szGSCCat, szGSCSubCat, "Finished finding files for gamescene -> GS2 conversion." );
	Tools_MessageLog::ShowOverlay( szGSCCat, szGSCSubCat );
}

void Tools_Mode_AnimTemplates::ConvertGamescene()
{
	// Find all files in the Asura_Root directory that contain entity data (.ent/.AsuraTemplate).
	// For each file:
	//     Load the static message data, convert it and save it back out.
	//     Parse all entity chunks, writing out neutral chunks and reading in, converting, and writing back out gamescene chunks.
	//     Output all messages to a logfile, to be checked back in with the entity file.

	Tools_ProgressIndicator xPI( "Gamescene -> GS2 Conversion" );
	Tools_MessageLog::DeleteMessages( szGSCCat, szGSCSubCat );
	Tools_MessageLog::AddMessage( szGSCCat, szGSCSubCat, "Starting gamescene -> GS2 conversion." );

	xPI.AddMessage( "Finding relevant files." );
	xPI.Update( 0, 0, true );
	Tools_MessageLog::AddMessage( szGSCCat, szGSCSubCat, "Finding relevant files." );

	// Find the files & check they are local and writable.
	List< Asura_String > xRelevantFiles;
	Tools_FileSystem::FindFiles( "*.ent", xRelevantFiles, ( TOOLS_FILEFIND_RECURSIVE | TOOLS_FILEFIND_IGNOREBACKUPDIR ) );
	Tools_FileSystem::FindFiles( "*.cut", xRelevantFiles, ( TOOLS_FILEFIND_RECURSIVE | TOOLS_FILEFIND_IGNOREBACKUPDIR ) );
	Tools_FileSystem::FindFiles( "*.AsuraTemplate", xRelevantFiles, ( TOOLS_FILEFIND_RECURSIVE | TOOLS_FILEFIND_IGNOREBACKUPDIR ) );

	const int iNumFiles = xRelevantFiles.size();
	xPI.AddMessage( "Found %d files.", iNumFiles );
	Tools_MessageLog::AddMessage( szGSCCat, szGSCSubCat, "Found %d files.", iNumFiles );

	xPI.AddMessage( "Checking files for writability." );
	xPI.Update( 0, 0, true );
	Tools_MessageLog::AddMessage( szGSCCat, szGSCSubCat, "Checking files for writability." );
	for ( LIF< Asura_String > xFilenameLIF( &xRelevantFiles ); !xFilenameLIF.done(); xFilenameLIF.next() )
	{
		if ( !Tools_FileSystem::IsWriteableLocalFile( xFilenameLIF().GetString() ) )
		{
			Tools_MessageLog::AddMessage( szGSCCat, szGSCSubCat, "File %s is not local and writable.", xFilenameLIF() );
			goto ExitInErrorWithLog;
		}
	}

	// If we haven't already loaded the anim templates then do so.
	if ( !Axon_Gamescene_AnimTemplate_System::GetAnimTemplateByIndex( 0 ) )
	{
		LoadAsuraFile( Tools_SaveAsura::GetAnimTemplatesFilename() );
	}

	// Iterate through the files.
	int iFile = 0;
	for ( LIF< Asura_String > xFilenameLIF( &xRelevantFiles ); !xFilenameLIF.done(); xFilenameLIF.next() )
	{
		xPI.AddMessage( "Converting file %s.", xFilenameLIF() );
		xPI.Update( iFile++, iNumFiles );
		Tools_MessageLog::AddMessage( szGSCCat, szGSCSubCat, "Converting file %s.", xFilenameLIF() );

		Tools_AsuraFileChunkLister xChunkLister;
		if ( !xChunkLister.LoadAsuraFile( xFilenameLIF().GetString(), false ) )
		{
			Tools_MessageLog::AddMessage( szGSCCat, szGSCSubCat, "Cannot open file %s.", xFilenameLIF() );
			goto ExitInErrorWithLog;
		}
		if ( !ConvertFileData( xFilenameLIF(), xChunkLister ) )
		{
			Tools_MessageLog::AddMessage( szGSCCat, szGSCSubCat, "Cannot convert file %s.", xFilenameLIF() );
			goto ExitInErrorWithLog;
		}

		xPI.AddMessage( "Finished file." );
		Tools_MessageLog::AddMessage( szGSCCat, szGSCSubCat, "Finished file." );
	}

	Tools_MessageLog::AddMessage( szGSCCat, szGSCSubCat, "Finished gamescene -> GS2 conversion." );
	Tools_MessageLog::ShowOverlay( szGSCCat, szGSCSubCat );
	return;

ExitInErrorWithLog:
	Tools_MessageLog::AddMessage( szGSCCat, szGSCSubCat, "Conversion has failed. Please revert all files and try again once the code's been written properly." );
	Tools_MessageLog::AddMessage( szGSCCat, szGSCSubCat, "Stopping gamescene -> GS2 conversion." );
	Tools_MessageLog::ShowOverlay( szGSCCat, szGSCSubCat );
}

void Tools_Mode_AnimTemplates::ConvertSpecificGamescene()
{
	Tools_FileSystem_DialogInfo	xFileSelector;
	xFileSelector.DefaultExtension		= ".ASR";
	xFileSelector.DefaultFilename		= "";
	xFileSelector.DefaultPath			= "Environments\\";
	xFileSelector.FileFilter			= "All Files\0*.*\0\0";
	xFileSelector.WindowTitle			= "Choose A File To Convert";
	xFileSelector.AllowMultipleSelect	= false;

	Asura_String xFileName = Tools_FileSystem::GetLoadFilename( xFileSelector );
	if ( xFileName.IsBlank() )
	{
		return;
	}

	// If we haven't already loaded the anim templates then do so.
	if ( !Axon_Gamescene_AnimTemplate_System::GetAnimTemplateByIndex( 0 ) )
	{
		LoadAsuraFile( Tools_SaveAsura::GetAnimTemplatesFilename() );
	}

	Tools_MessageLog::DeleteMessages( szGSCCat, szGSCSubCat );
	Tools_MessageLog::AddMessage( szGSCCat, szGSCSubCat, "Starting gamescene -> GS2 conversion." );

	if ( !Tools_FileSystem::IsWriteableLocalFile( xFileName.GetString() ) )
	{
		Tools_MessageLog::AddMessage( szGSCCat, szGSCSubCat, "File %s is not local and writable.", xFileName );
		goto ExitInErrorWithLog;
	}

	Tools_MessageLog::AddMessage( szGSCCat, szGSCSubCat, "Converting file %s.", xFileName );

	{
	Tools_AsuraFileChunkLister xChunkLister;
	if ( !xChunkLister.LoadAsuraFile( xFileName.GetString(), false ) )
	{
		Tools_MessageLog::AddMessage( szGSCCat, szGSCSubCat, "Cannot open file %s.", xFileName );
		goto ExitInErrorWithLog;
	}
	if ( !ConvertFileData( xFileName, xChunkLister ) )
	{
		Tools_MessageLog::AddMessage( szGSCCat, szGSCSubCat, "Cannot convert file %s.", xFileName );
		goto ExitInErrorWithLog;
	}
	}

	Tools_MessageLog::AddMessage( szGSCCat, szGSCSubCat, "Finished file." );

	Tools_MessageLog::AddMessage( szGSCCat, szGSCSubCat, "Finished gamescene -> GS2 conversion." );
	Tools_MessageLog::ShowOverlay( szGSCCat, szGSCSubCat );
	return;

ExitInErrorWithLog:
	Tools_MessageLog::AddMessage( szGSCCat, szGSCSubCat, "Conversion has failed. Please revert all files and try again." );
	Tools_MessageLog::AddMessage( szGSCCat, szGSCSubCat, "Stopping gamescene -> GS2 conversion." );
	Tools_MessageLog::ShowOverlay( szGSCCat, szGSCSubCat );}
#endif

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

#undef szTEMPLATES_DIRECTORY
#undef szTEMPLATES_EXTENSION
#undef szPACKAGES_DIRECTORY
#undef szPACKAGES_EXTENSION
#undef szFIDGET
#undef szLOOPING
#undef szRIGID
#undef szSTATIC
#undef szFIDGET_DIRECTORY
#undef szLOOPING_DIRECTORY
#undef szRIGID_DIRECTORY
#undef szSTATIC_DIRECTORY

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

#endif // ASURA_USE_AXON
