#include "Core/Asura.h"

#include "Cutscene/Tools_Cutscene_Object.h"

#include "Core/Asura_MemoryManagement_DebugOff.h" // Std headers define their own new and delete operators and our macros break them.
#pragma warning( push )
#pragma warning( disable : 4702 ) // unreachable code
#include <algorithm>
#pragma warning( pop )
#include "Core/Asura_MemoryManagement_DebugOn.h"

#include "Camera/Asura_Camera.h"
#include "ChunkSystem/Asura_SaveBlock.h"
#include "ChunkSystem/Asura_Chunk_Cutscene.h"
#include "ChunkSystem/Asura_Chunk_Texture.h"
#include "Core/Asura_HashedLocalisedText.h"
#include "Core/Memory/Asura_ResourceAllocator.h"
#include "Core/Asura_SortAndSearch.h"
#include "Cutscene/Asura_Cutscene.h"
#include "Cutscene/Asura_Cutscene_System.h"
#include "Cutscene/Asura_Cutscene_Actor.h"
#include "Cutscene/Asura_Cutscene_Attachment.h"
#include "Cutscene/Asura_Cutscene_Event.h"
#include "Cutscene/Asura_Cutscene_Track.h"
#include "Debugging/Asura_Assert.h"
#include "Entity/Cutscene/Asura_ServerEntity_CutsceneController.h"
#include "Entity/Asura_StaticMessage_System.h"
#include "Interface/Asura_Overlay_System.h"
#include "Interface/Asura_Overlay.h"
#include "Interface/Asura_Dialog.h"
#include "Mesh/Asura_Mesh_System.h"
#include "Rendering/Asura_Text_Output.h"
#include "Sound/Asura_Audio_System.h"

#include "Tools_System.h"
#include "Tools_Overlays.h"
#include "Cutscene/Tools_CutsceneActor_Object.h"
#include "Cutscene/Tools_Mode_Cutscene.h"
#include "Cutscene/Tools_Cutscene_Event.h"
#include "Cutscene/Tools_Cutscene_Track.h"
#include "Cutscene/Tools_WavWriter.h"
#include "Cutscene/Tools_Cutscene_Dialog.h"
#include "Cutscene/Tools_Cutscene_Subtitles.h"
#include "Cutscene/Tools_Cutscene_Timeline.h"
#include "Entity/Tools_MessageEditor.h"
#include "Entity/Tools_Entity_CutsceneController_Object.h"
#include "Interface/Tools_ContextMenu_System.h"
#include "Lighting/Tools_LightSets.h"
#include "Load&Save/Tools_SaveAsura.h"
#include "Misc/Tools_Modal_Overlay.h"
#include "Misc/Tools_Timed_Messages.h"
#include "Objects/Tools_Object_System.h"
#include "PC/FileSystem/Tools_FileSystem.h"														  
#include "Sound/Tools_PickSound.h"
#include "Sound/Tools_SoundMixer_Overlay.h"
#include "Sound/Tools_Sound_System.h"

bool Tools_Cutscene_Object::s_bMoveCutsceneMembersWithCutsceneObject;

Asura_Tabs_Dialog* Tools_Cutscene_Object::s_pxCutsceneTabs = NULL;

#define CALLBACK_VOID(__Function) Asura_Callback_MemberFunction<void,Tools_Cutscene_Object>(this, &Tools_Cutscene_Object::__Function) 
#define CALLBACK_BOOL(__Function) Asura_Callback_MemberFunction<bool,Tools_Cutscene_Object>(this, &Tools_Cutscene_Object::__Function) 



Tools_Cutscene_Object::Tools_Cutscene_Object(Asura_Cutscene* _Cutscene)
: Tools_Object(TOT_Cutscene)
{
	ConvertInitialAnimations(_Cutscene);

	Cutscene = _Cutscene;
	Orientation.Identity();
	UpdatePositionFromAsuraData();

	Asura_String mesh_name = "ToolsShape_Cutscene";
	Mesh = Asura_Mesh_System::GetMesh(mesh_name);

	SelectedEvent = 0;
	SelectedEventIndex = 0;
	SelectedEventIndexChanged = false;
	m_bChanged = false;
	m_bUseSoundTrackChanged = false;
	m_bTakeLengthFromSoundTrackChanged = false;
	m_iPreviousScriptLine = -1;
	m_bEventSortRequired = false;
	m_iPreviousNumMessages = 0;

	UpdateBackupVariables();

	m_pxCutsceneController = 0;
	//search for the cutscene controller entity
	for(Asura_Hashed_List_It<Asura_ServerEntity_CutsceneController> xIt(Asura_ServerEntity_CutsceneController::GetList()); !xIt.Done(); xIt.Next())
	{
		Asura_ServerEntity_CutsceneController* pxEntity = xIt.GetCurrent();
		if(!pxEntity->GetCutsceneName()->Compare(Cutscene->GetName()))
		{
			m_pxCutsceneController = pxEntity;
			break;
		}
	}
}


Tools_Cutscene_Object::~Tools_Cutscene_Object()
{
	DeleteChildObjects();
	Asura_Assert(!m_xSoundList.size(),"Sounds should already have been stopped");
}

void Tools_Cutscene_Object::CreateAllCutsceneObjects()
{
	Asura_Assert(Tools_Mode_Cutscene::s_pxThis,"");

	int numberOfCutscenes = Asura_Cutscene_System::GetNumberOfCutscenes();
	for(int i=0;i<numberOfCutscenes;i++)
	{
		Tools_Mode_Cutscene::s_pxThis->CreateCutsceneObject(Asura_Cutscene_System::GetCutsceneByIndex(i));
	}

}

void Tools_Cutscene_Object::UpdatePositionFromAsuraData()
{
	Position = Cutscene->Position;
}

void Tools_Cutscene_Object::UpdatePositionInAsuraData(bool /*updateLinkedObjects*/)
{
	Cutscene->Position = Position;
	Tools_SaveAsura::SetCutscenesModified();

	if(CutsceneUndo && this == Tools_Mode_Cutscene::CurrentCutscene)
	{
		CutsceneUndo->Log_Cutscene_Changed(this);
	}
}

void Tools_Cutscene_Object::CalculateRelativePosition(Tools_Object* pxObject)
{
	Tools_Object::CalculateRelativePosition(pxObject);

	//get tracks etc to update their relative position stores
	Asura_Matrix_3x3 xOrientInverse = Orientation;
	xOrientInverse.Transpose();

	Cutscene->CalculateRelativePosition(xOrientInverse,Position);

	if(s_bMoveCutsceneMembersWithCutsceneObject)
	{
		//get a list of the child actor and track objects
		List<Tools_Object*> xObjectList;
		for(LIF<Tools_CutsceneActor_Object*> xActorLIF(&ActorObjects);!xActorLIF.done();xActorLIF.next())
		{
			xActorLIF()->ListObjectsForActor(xObjectList);
		}
		//calculate relative position for each of those
		for(LIF<Tools_Object*> xObjectLIF(&xObjectList); !xObjectLIF.done(); xObjectLIF.next())
		{
			xObjectLIF()->CalculateRelativePosition(pxObject);
		}
	}
}

void Tools_Cutscene_Object::UpdateRelative(Tools_Object* pxObject)
{
	Tools_Object::UpdateRelative(pxObject);

	//update tracks etc relative to the new cutscene position
	if(s_bMoveCutsceneMembersWithCutsceneObject /*&& this != Tools_Mode_Cutscene::CurrentCutscene*/)
	{
		Cutscene->UpdateRelativeToPosition(Orientation,Position);
	}

	if(s_bMoveCutsceneMembersWithCutsceneObject)
	{
		//get a list of the child actor and track objects
		List<Tools_Object*> xObjectList;
		for(LIF<Tools_CutsceneActor_Object*> xActorLIF(&ActorObjects);!xActorLIF.done();xActorLIF.next())
		{
			xActorLIF()->ListObjectsForActor(xObjectList);
		}
		//update relative position for each of those
		for(LIF<Tools_Object*> xObjectLIF(&xObjectList); !xObjectLIF.done(); xObjectLIF.next())
		{
			xObjectLIF()->UpdateRelative(pxObject);
		}
	}
}

void Tools_Cutscene_Object::Edit()
{
	if(Tools_System::CurrentMode == TOOLS_MODE_CUTSCENE)
	{
		UpdateBackupVariables();
		Tools_Mode_Cutscene::s_pxThis->EditCutscene(this);
	}	
}

const char* Tools_Cutscene_Object::GetName()
{
	return Cutscene->CutsceneName.GetString();
}

void Tools_Cutscene_Object::PrintInfo(float& x,float& y)
{
	Tools_Object::PrintInfo(x,y);
	Asura_Text_Output::Print(x,y++,0xff00ff00,0,"E : Edit Cutscene");
	RenderSubtitle(x,y);
}

void Tools_Cutscene_Object::Destroy()
{
	//stop editing this cutscene
	Tools_Mode_Cutscene::s_pxThis->StopPlayback();
	if(Tools_Mode_Cutscene::CurrentCutscene==this)
	{
		Tools_Mode_Cutscene::s_pxThis->EditCutscene(0);
	}
	Tools_SaveAsura::SetCutscenesModified();

	//delete the cutscene
	Cutscene->PreDestructionCleanup();
	Asura_Cutscene_System::DeleteCutscene( Cutscene->CutsceneName.GetString() );
	Cutscene = NULL;

	Tools_Object::Destroy();

	//make sure the cutscene controller entity get killed as well
	Tools_Entity_CutsceneController_Object::UpdateCutsceneControllersFromCutscenes();
}

void Tools_Cutscene_Object::AddOverlayControls(class Asura_Overlay* pxOverlay,Asura_Vector_2& xOffset)
{
	Asura_Dialog* pxDialog;

	//start of tabs
	Asura_Tabs_Dialog* pxTabsDialog = s_pxCutsceneTabs = new Asura_Tabs_Dialog(xOffset, 0, 0);
	pxOverlay->AddDialog(pxTabsDialog);
	xOffset.y += Asura_Dialog::LargeSpacing();
	pxOverlay->SetCurrentParentDialog(pxTabsDialog);

	float fTabsStartY = xOffset.y;
	float fTabsEndY = xOffset.y;

	//start of main tab
	pxTabsDialog->AddTab("Main");

	#if ALLOW_CUTSCENE_EXPORT
	pxDialog = new Asura_Function_Dialog("Export Cutscene",xOffset,Tools_Mode_Cutscene::_ExportCutscene);
	pxDialog->SetHelpText("Exports this cutscene to a seperate cutscene file , which can then be reimported into another levle.\nLinks to other entities won't be preserved.");
	pxOverlay->AddDialog(pxDialog);
	xOffset.y+=Asura_Dialog::DefaultSpacing();
	#endif

	// SD: Show cutscene name (it is shown no where else !)
	pxDialog = new Asura_Function_Dialog(Cutscene->CutsceneName.GetString(),xOffset,Tools_Mode_Cutscene::_EditCutsceneName);
	pxDialog->SetHelpText("Rename this cutscene.");
	pxOverlay->AddDialog(pxDialog);
	xOffset.y+=Asura_Dialog::DefaultSpacing();

	pxDialog = new Asura_Function_Dialog("Reload Anims",xOffset,Tools_Mode_Cutscene::_ReloadAnimations);
	pxDialog->SetHelpText("Forces all the animations used by this cutscene to be reloaded from their original files..");
	pxOverlay->AddDialog(pxDialog);
	xOffset.y+=Asura_Dialog::DefaultSpacing();


		
	pxDialog = new Asura_Function_Dialog("",xOffset,Tools_Mode_Cutscene::_EditCutsceneDirectory);
	pxDialog->SetHelpText("Set optional subdirectory of 'Cutscenes' directory to get extra animations and skins from.");
	pxOverlay->AddDialog(pxDialog);
	pxDialog->m_iControlID = DialogID_CutsceneDirectory;
	xOffset.y+=Asura_Dialog::DefaultSpacing();


	pxDialog = new Tools_CutsceneTime_Dialog("Start Time",xOffset,&Cutscene->m_fCutsceneStartTime,0,1000,0.1f,&m_bChanged);
	pxDialog->SetHelpText("Start time for the cutscene.\nIf used , then the cutscene will immediately skip forward to the start time when played.\nNote that this won't cause time to be skipped on the soundtrack (if any).");
	pxOverlay->AddDialog(pxDialog);
	xOffset.y+=Asura_Dialog::DefaultSpacing();

	pxDialog = new Tools_CutsceneTime_Dialog("End Time",xOffset,&Cutscene->CutsceneEndTime,0,1000,0.1f,&m_bChanged);
	pxDialog->SetHelpText("Time at which to end cutscene.\nIf set to 0 , then the cutscene never ends.");
	pxOverlay->AddDialog(pxDialog);
	xOffset.y+=Asura_Dialog::LargeSpacing();

	pxDialog = new Asura_Function_Dialog("Create Actor",xOffset,Tools_Mode_Cutscene::_CreateActor);
	pxDialog->SetHelpText("Create a new actor for the cutscene.");
	pxOverlay->AddDialog(pxDialog);
	xOffset.y+=Asura_Dialog::DefaultSpacing();

	pxDialog = new Asura_Function_Dialog("Copy Actor",xOffset,Tools_Mode_Cutscene::_CopyActor);
	pxDialog->SetHelpText("Copy an actor (including tracks) from this or another cutscene.");
	pxOverlay->AddDialog(pxDialog);
	xOffset.y+=Asura_Dialog::DefaultSpacing();

	pxDialog = new Asura_Function_Dialog("Duplicate Selected Actors",xOffset,Tools_Mode_Cutscene::_DuplicateSelectedActors);
	pxDialog->SetHelpText("Makes a copy of all the selected actors (including tracks).");
	pxOverlay->AddDialog(pxDialog);
	xOffset.y+=Asura_Dialog::DefaultSpacing();

	pxDialog = new Asura_Function_Dialog("Reorder Actors",xOffset,ReorderActorsForTimeline);
	pxDialog->SetHelpText("Changes the order in which the actors are displayed in the timeline screen.");
	pxOverlay->AddDialog(pxDialog);
	xOffset.y+=Asura_Dialog::LargeSpacing();



	pxDialog = new Asura_Function_Dialog("Create Event",xOffset,Tools_Mode_Cutscene::_CreateCutsceneEvent);
	pxDialog->SetHelpText("Create a new event for this cutscene.");
	pxOverlay->AddDialog(pxDialog);
	xOffset.y+=Asura_Dialog::DefaultSpacing();

	pxDialog = new Asura_Function_Dialog("Edit Events",xOffset,Tools_Mode_Cutscene::_EditEvent);
	pxDialog->SetHelpText("Brings up the event editing overlay (if any exist).");
	pxOverlay->AddDialog(pxDialog);
	xOffset.y+=Asura_Dialog::DefaultSpacing();

	pxDialog = new Asura_Function_Dialog("Edit Vis. Events",xOffset,Tools_Cutscene_Timeline::EditHiddenEvents);
	pxDialog->SetHelpText("Determines which event types should be visble on the timeline bar below.\nThis can also be done by pressing E while the cursor is over the bar.");
	pxOverlay->AddDialog(pxDialog);
	xOffset.y+=Asura_Dialog::LargeSpacing();

//	#ifndef ASURA_TOOLS_USE_NEW_MESSAGE_EDITOR
	char szText[100];
	m_iPreviousNumMessages = Asura_StaticMessage_System::GetNumberOfMessagesInBlock(Cutscene->m_usEndCutsceneMessageBlock);
	Asura_PrintfToBfr(szText,"Edit End Cutscene Message (%d)",sizeof(szText),true,m_iPreviousNumMessages);

	pxDialog = new Asura_Function_Dialog(szText,xOffset,EditMessageBlock);
	pxDialog->SetHelpText("Edit messags to be sent to other entities when this cutscene ends.");
	pxDialog->m_iControlID = DialogID_EditEndCutsceneMessage;
	pxOverlay->AddDialog(pxDialog);
	xOffset.y+=Asura_Dialog::DefaultSpacing();
//	#endif

	pxDialog = new Asura_Function_Dialog("Record Wav File",xOffset,Tools_Mode_Cutscene::_RecordWavFile);
	pxDialog->SetHelpText("Records a single wav file using the settings defined in CutsceneText.txt.\nThis will be set as the sound track for the cutscene.");
	pxOverlay->AddDialog(pxDialog);
	xOffset.y+=Asura_Dialog::DefaultSpacing();

	pxDialog = new Asura_Function_Dialog("Soundtrack",xOffset,Tools_Mode_Cutscene::_PickWavFile);
	pxDialog->SetHelpText("Choose the file to use for the soundtrack.");
	pxDialog->m_iControlID = DialogID_SoundTrack;
	pxOverlay->AddDialog(pxDialog);		
	xOffset.y+=Asura_Dialog::DefaultSpacing();

	pxDialog = new Asura_Numerical_Dialog("Sound Track Volume",xOffset,&Cutscene->m_fSoundTrackVolume,0.0f,1.5f,0.01f,"%.2f",&m_bChanged);
	pxDialog->SetHelpText("Volume multiplier for cutscene sound track.\nNote that 1.33 is effectively maximum , since all volumes are scaled by 0.75 and capped at 1.0 at the end.");
	pxOverlay->AddDialog(pxDialog);
	xOffset.y+=Asura_Dialog::DefaultSpacing();

	pxDialog = new Tools_SoundMixerPreset_DropDownList_Dialog( "Mixer Preset", xOffset, 10, &Cutscene->m_uMixerPreset, &m_bChanged );
	pxDialog->SetHelpText("This mixer preset will be 'queued' when the cutscene is playing , if set to something other than none or default.\nIt may not have any effect if there are other mixers with higher priority active.");
	pxOverlay->AddDialog(pxDialog);
	xOffset.y+=Asura_Dialog::DefaultSpacing();

	//end of main tab
	if(xOffset.y > fTabsEndY) fTabsEndY = xOffset.y;

	//start of flags tab
	pxTabsDialog->AddTab("Flags");
	xOffset.y = fTabsStartY;



	pxDialog = new Asura_FlagTickbox_Dialog("End At Last Point",xOffset,&Cutscene->m_iCutsceneFlags,AsuraCutsceneFlag_EndAtLastTrackPoint,&m_bChanged);
	pxDialog->SetHelpText("Automatically set cutscene to end when the last track point has been reached.");
	pxOverlay->AddDialog(pxDialog);
	xOffset.y+=Asura_Dialog::DefaultSpacing();

	pxDialog = new Asura_FlagTickbox_Dialog("Loop Cutscene",xOffset,&Cutscene->m_iCutsceneFlags,AsuraCutsceneFlag_LoopCutscene,&m_bChanged);
	pxOverlay->AddDialog(pxDialog);
	xOffset.y+=Asura_Dialog::DefaultSpacing();

	pxDialog = new Asura_FlagTickbox_Dialog("Loop To Start Time",xOffset,&Cutscene->m_iCutsceneFlags,AsuraCutsceneFlag_LoopToStartTime,&m_bChanged);
	pxDialog->SetHelpText("Causes a looped cutscene to loop back to the start time , instead of back to zero");
	pxDialog->SetIsEnabledFunction(CALLBACK_BOOL(IsEnabled_LoopToStartTime));
	pxOverlay->AddDialog(pxDialog);
	xOffset.y+=Asura_Dialog::DefaultSpacing();


	pxDialog = new Asura_FlagTickbox_Dialog("Widescreen",xOffset,&Cutscene->m_iCutsceneFlags,AsuraCutsceneFlag_Widescreen,&m_bChanged);
	pxDialog->SetHelpText("Draws widescreen bars at top/bottom of screen.");
	pxOverlay->AddDialog(pxDialog);
	xOffset.y+=Asura_Dialog::DefaultSpacing();

	pxDialog = new Asura_FlagTickbox_Dialog("Starts Active",xOffset,&Cutscene->m_iCutsceneFlags,AsuraCutsceneFlag_StartsActive,&m_bChanged);
	pxDialog->SetHelpText("Cutscene immediately starts playing at start of level.");
	pxOverlay->AddDialog(pxDialog);
	xOffset.y+=Asura_Dialog::DefaultSpacing();

	pxDialog = new Asura_FlagTickbox_Dialog("Ignore Module Linking",xOffset,&Cutscene->m_iCutsceneFlags,AsuraCutsceneFlag_IgnoreModuleLinking,&m_bChanged);
	pxDialog->SetHelpText("Actors in cutscene are rendered regardless of module linking.\nNeeded for actors that move in areas not covered by modules.");
	pxOverlay->AddDialog(pxDialog);
	xOffset.y+=Asura_Dialog::DefaultSpacing();

	pxDialog = new Asura_FlagTickbox_Dialog("Disable Camera Control",xOffset,&Cutscene->m_iCutsceneFlags,AsuraCutsceneFlag_DisableCamera,&m_bChanged);
	pxDialog->SetHelpText("Disables camera control for this cutscene , primarily for testing purposes.\nThis is similar to the disable camera option on the cutscene playback overlay , except that it will also affect playback in game.");
	pxOverlay->AddDialog(pxDialog);
	xOffset.y+=Asura_Dialog::DefaultSpacing();

	pxDialog = new Asura_FlagTickbox_Dialog("Skybox",xOffset,&Cutscene->m_iCutsceneFlags,AsuraCutsceneFlag_Skybox,&m_bChanged);
	pxDialog->SetHelpText("Offsets all the actors so that they will be rendered in the skybox.");
	pxOverlay->AddDialog(pxDialog);
	xOffset.y+=Asura_Dialog::DefaultSpacing();

	pxDialog = new Asura_FlagTickbox_Dialog("Skippable",xOffset,&Cutscene->m_iCutsceneFlags,AsuraCutsceneFlag_Skippable,&m_bChanged);
	pxDialog->SetHelpText("The player can skip this cutscene by pressing an appropriate key.");
	pxOverlay->AddDialog(pxDialog);
	xOffset.y+=Asura_Dialog::DefaultSpacing();

	pxDialog = new Asura_Tickbox_Dialog("Has Sound Track",xOffset,&Cutscene->UseSoundTrack,&m_bUseSoundTrackChanged);
	pxDialog->SetHelpText("This cutscene has a sound track that should be played at the same time.\nAutomatically turned on when the sound track is first selected.");
	pxOverlay->AddDialog(pxDialog);
	xOffset.y+=Asura_Dialog::DefaultSpacing();

	pxDialog = new Asura_Tickbox_Dialog( "Soundtrack Contains Music", xOffset, &Cutscene->m_bSoundTrackContainsMusic );
	pxDialog->SetHelpText( "Does the soundtrack contain music (so we can override user music on 360 to prevent it overlapping)." );
	pxOverlay->AddDialog(pxDialog);
	xOffset.y += Asura_Dialog::DefaultSpacing();

	pxDialog = new Asura_FlagTickbox_Dialog("Don't Pause Streams",xOffset,&Cutscene->m_iCutsceneFlags,AsuraCutsceneFlag_DontPauseStreams,&m_bChanged);
	pxDialog->SetHelpText("This cutscene will not cause any other streams or music to stop when it plays.\nThere must be enough streams available for it to play its soundtrack.");
	pxOverlay->AddDialog(pxDialog);
	xOffset.y+=Asura_Dialog::DefaultSpacing();


	pxDialog = new Asura_FlagTickbox_Dialog("Use Sound Track Length",xOffset,&Cutscene->m_iCutsceneFlags,AsuraCutsceneFlag_TakeLengthFromSoundTrack,&m_bTakeLengthFromSoundTrackChanged);
	pxDialog->SetHelpText("If this is used then the cutscene end time will automatically be updated to match the length of the sound track (if there is one).");
	pxOverlay->AddDialog(pxDialog);
	xOffset.y+=Asura_Dialog::LargeSpacing();


	pxDialog = new Asura_FlagTickbox_Dialog("Include Skins in Main File",xOffset,&Cutscene->m_iCutsceneFlags,AsuraCutsceneFlag_ForceIntoMainFile,&m_bChanged);
	pxDialog->SetHelpText("For PS2 exports. Force this cutscenes skins to be included in the main level export file.");
	pxOverlay->AddDialog(pxDialog);
	xOffset.y+=Asura_Dialog::DefaultSpacing();

	pxDialog = new Asura_FlagTickbox_Dialog("Always Include in Export",xOffset,&Cutscene->m_iCutsceneFlags,AsuraCutsceneFlag_AlwaysIncludeInExport,&m_bChanged);
	pxDialog->SetHelpText("Force this cutscene to be exported , even if the no cutscene option is chosen on the level export overlay.");
	pxOverlay->AddDialog(pxDialog);
	xOffset.y+=Asura_Dialog::DefaultSpacing();

	pxDialog = new Asura_FlagTickbox_Dialog("Never Include in Export",xOffset,&Cutscene->m_iCutsceneFlags,AsuraCutsceneFlag_NeverIncludeInExport,&m_bChanged);
	pxDialog->SetHelpText("This cutscene should never be included in level exports.");
	pxOverlay->AddDialog(pxDialog);
	xOffset.y+=Asura_Dialog::DefaultSpacing();

	pxDialog = new Asura_FlagTickbox_Dialog("Don't Export To PS2",xOffset,&Cutscene->m_iCutsceneFlags,AsuraCutsceneFlag_DontExportToPS2,&m_bChanged);
	pxDialog->SetHelpText("Don't include this cutscene (and associated skins and animations) in PS2 level exports.");
	pxOverlay->AddDialog(pxDialog);
	xOffset.y+=Asura_Dialog::DefaultSpacing();

	pxDialog = new Asura_FlagTickbox_Dialog("Don't Export To GC",xOffset,&Cutscene->m_iCutsceneFlags,AsuraCutsceneFlag_DontExportToGC,&m_bChanged);
	pxDialog->SetHelpText("Don't include this cutscene (and associated skins and animations) in Gamecube level exports.");
	pxOverlay->AddDialog(pxDialog);
	xOffset.y+=Asura_Dialog::LargeSpacing();

	//end of flags tab
	if(xOffset.y > fTabsEndY) fTabsEndY = xOffset.y;

	if(Tools_LightSets::IsUsingLightSets())
	{
		//start of light sets tab
		pxTabsDialog->AddTab("Light sets");
		xOffset.y = fTabsStartY;

		for(u_int i=0;i<TOOLS_MAX_LIGHT_SETS;i++)
		{
			const char* szSetName = Tools_LightSets::GetLightSetNameByIndex(i);
			if(!szSetName) continue;


			pxDialog = new Asura_FlagTickbox_Dialog(szSetName, xOffset, &Cutscene->m_uLightSets, 1<<i, &m_bChanged);
			pxDialog->SetHelpText("Enables this cutscene for this light set.");
			pxOverlay->AddDialog(pxDialog);
			xOffset.y+=Asura_Dialog::DefaultSpacing();
		}


		//end of light sets tab
		if(xOffset.y > fTabsEndY) fTabsEndY = xOffset.y;
	}


	//end of tabs
	xOffset.y = fTabsEndY + 1.0f;
	pxOverlay->SetCurrentParentDialog(0);

	static u_int s_uSelectedTab = 0;
	pxTabsDialog->SetSelectedTabPointer(&s_uSelectedTab);

	//set things up so that right clicking on the overlay will be equivalent to right clicking on the object
	//RAR 06/11/2007
	pxOverlay->SetContextMenuCallback(Tools_Object_System::AddContextMenuItemsFromOverlay, this);


}

void Tools_Cutscene_Object::UpdateOverlayLabels()
{
	Asura_Overlay* pxOverlay = Asura_Overlay_System::GetOverlay(TOOLS_OVERLAY_CUTSCENE);
	if(!pxOverlay) return;
	
	char szText[500];
	//soundtrack text
	Asura_Dialog* pxDialog = pxOverlay->GetDialog(DialogID_SoundTrack);
	if(pxDialog)
	{
		if(Cutscene->UseSoundTrack)
		{
			if(Cutscene->SoundTrackName.IsBlank())
			{
				strcpy(szText,"Soundtrack : *None*");
			}
			else
			{
				Asura_PrintfToBfr(szText,"Soundtrack : %s",sizeof(szText),true,Tools_FileSystem::StripPath(Cutscene->SoundTrackName.GetString()));
			}
		}
		else
		{
			strcpy(szText,"Soundtrack : *Not Used*");
		}
		pxDialog->UpdateLabel(szText);

		//put the full names in the help text
		if(Cutscene->UseSoundTrack && !Cutscene->SoundTrackName.IsBlank())
		{
			char szLocalFilename[MAX_PATH];
			Cutscene->LocaliseSoundName(Cutscene->SoundTrackName.GetString(),szLocalFilename,MAX_PATH);

			Asura_PrintfToBfr(szText,"Choose the file to use for the soundtrack.\nUnlocalised : %s\nLocalised : %s",sizeof(szText),true,Cutscene->SoundTrackName.GetString(),szLocalFilename);
		}
		else
		{
			strcpy(szText,"Choose the file to use for the soundtrack");
		}
		pxDialog->SetHelpText(szText);
	}

	//update directory text
	const char* pszDirectory = GetCutsceneDirectory();
	if(pszDirectory)
	{
		sprintf(szText,"Directory : %s",pszDirectory);
	}
	else
	{
		strcpy(szText,   "Directory : (None)");
	}

	pxOverlay->UpdateDialogLabel(DialogID_CutsceneDirectory,szText);

	//Edit End Cutscene Message
	pxDialog = pxOverlay->GetDialog(DialogID_EditEndCutsceneMessage);
	if(pxDialog)
	{
		m_iPreviousNumMessages = Asura_StaticMessage_System::GetNumberOfMessagesInBlock(Cutscene->m_usEndCutsceneMessageBlock);
		Asura_PrintfToBfr(szText,"Edit End Cutscene Message (%d)",sizeof(szText),true,m_iPreviousNumMessages);

		pxDialog->UpdateLabel(szText);
	}

	

	
}

void Tools_Cutscene_Object::UpdateCutsceneEdit()
{

	if(m_bUseSoundTrackChanged)
	{
		UpdateOverlayLabels();
		UpdateLengthFromSoundTrack();
		m_bUseSoundTrackChanged = false;
		m_bChanged = true;
	}

	if(m_bTakeLengthFromSoundTrackChanged)
	{
		UpdateLengthFromSoundTrack();
		m_bTakeLengthFromSoundTrackChanged = false;
		m_bChanged = true;
	}

	if(Asura_StaticMessage_System::GetNumberOfMessagesInBlock(Cutscene->m_usEndCutsceneMessageBlock) != m_iPreviousNumMessages)
	{
		UpdateOverlayLabels();
	}

	
	if(m_bChanged)
	{
		Tools_SaveAsura::SetCutscenesModified();
		Tools_Mode_Cutscene::RefreshPlayback();
		m_bChanged = false;
	}
	
	for(LIF<Tools_Cutscene_Event*> elif(&EventList);!elif.done();elif.next())
	{
		elif()->Update();
	}

	if(m_bEventSortRequired)
	{
		SortEvents();
	}

	if(SelectedEventIndexChanged)
	{
		int index = (int) SelectedEventIndex;
		Asura_Assert(index>=0 && index<EventList.size(),"");
		SelectEvent(EventList[index]);
	}
	for(LIF<Tools_CutsceneActor_Object*> alif(&ActorObjects);!alif.done();alif.next())
	{
		alif()->UpdateCutsceneEdit();
	}


	//update positions for events
	for(LIF<Tools_Cutscene_Event*> elif(&EventList);!elif.done();elif.next())
	{
		elif()->UpdatePosition();
	}
}

void Tools_Cutscene_Object::StartCutsceneEdit()
{
	//update cutscene length if needed
	UpdateLengthFromSoundTrack();
}

void Tools_Cutscene_Object::FinishCutsceneEdit()
{
	Asura_Overlay_System::CloseOverlays(TOOLS_OVERLAY_CUTSCENE);
	Asura_Overlay_System::CloseOverlays(TOOLS_OVERLAY_CUTSCENE_PLAYBACK);
	Asura_Overlay_System::CloseOverlays(TOOLS_OVERLAY_CUTSCENE_PLAYBACKSLIDER);

	UpdateTracks();
	DeleteChildObjects();
}

void Tools_Cutscene_Object::CreateChildObjects()
{
	//create actor objects for the actors
	for(int i=0;i<Cutscene->NumberOfActors;i++)
	{
		Tools_CutsceneActor_Object* actorObject = AllocateToolsActor(Cutscene->Actors[i]);
		actorObject->Initialise();

	}

	//create the tools events jobbies
	for(int i=0;i<Cutscene->NumberOfEvents;i++)
	{
		Tools_Cutscene_Event* toolsEvent = AllocateToolsEvent(Cutscene->Events[i],-1);
		toolsEvent->UpdateBackupVariables();
	}
	//get ParentEventIds from the parent event indeces
	for(LIF<Tools_Cutscene_Event*> elif(&EventList);!elif.done();elif.next())
	{
		elif()->UpdateEventIDsFromIndeces();
		
	}

	UpdateEventIndeces();
}

Tools_CutsceneActor_Object* Tools_Cutscene_Object::AllocateToolsActor(Asura_Cutscene_Actor* actor)
{
	return new Tools_CutsceneActor_Object(actor,this);
}

void Tools_Cutscene_Object::DeleteChildObjects()
{
	while(ActorObjects.size())
	{
		Tools_Object_System::DeleteObject(ActorObjects.first_entry());
	}

	SelectEvent(0);

	while(EventList.size())
	{
		delete EventList.first_entry();
	}
}

void Tools_Cutscene_Object::CreateActor()
{
	//select the type for the new actor
	int actorType = SelectActorType();
	if(actorType<0) return;

	CreateActor(actorType);
}

int Tools_Cutscene_Object::SelectActorType()
{
	//add the actor types to an overlay
	ListSelection_Overlay overlay("Select Actor Type :");

	overlay.AddItem("Camera",ACAT_Camera);
	overlay.AddItem("Camera Target",ACAT_CameraTarget);
	overlay.AddItem("Hierarchy",ACAT_Hierarchy);
	overlay.AddItem("Shape",ACAT_Shape);

	if(!overlay.DoModal()) return -1;

	return overlay.GetSelectionData();
	
}

Asura_Cutscene_Actor* Tools_Cutscene_Object::AllocateActor( int iActorType, const Asura_String& xActorName )
{
    Asura_Cutscene_Actor* pxActor = Asura_Cutscene_Actor::AllocateActor( iActorType, false, Cutscene );
    
    if( pxActor )
    {
        pxActor->ActorName = xActorName;
    }

    return pxActor;
}

bool Tools_Cutscene_Object::CheckNameOK(const Asura_String& xName)
{
	bool bRet = true;
	for(int i=0;i<Cutscene->NumberOfActors;i++)
	{
		if(Cutscene->Actors[i]->ActorName == xName)
		{
			bRet = false;
		}
	}
	return bRet;
}

Tools_CutsceneActor_Object* Tools_Cutscene_Object::CreateActor(int actorType,const char* name /*=NULL*/,Asura_Guid uGuid /*= ASURA_GUID_UNREGISTERED*/,bool bCreateTrack /*= true*/)
{
	Asura_String actorName;
	
	
	if(name)
	{
		actorName = name;
	}
	else
	{
		Asura_String xDefaultName = "";
		if(actorType == ACAT_Camera || actorType == ACAT_CameraTarget)
		{
			const char* pszBaseName = ( actorType == ACAT_Camera ? "Camera" : "Camera Target" );
        
			//search for an okay default name
			for(int iIndex=0;;iIndex++)
			{
				char szName[50];
				Asura_PrintfToBfr(szName,"%s %d",50,true,pszBaseName,iIndex);

				xDefaultName = szName;
				if(CheckNameOK(xDefaultName)) break;

			}
		}
			
		//need to pick a name
		GetString_Overlay overlay("Choose name for actor :",xDefaultName.GetString());
		if(!overlay.DoModal()) return 0;
			
		actorName = overlay.GetString();
		if(!actorName.GetLength()) return 0;

		bool nameOK = CheckNameOK(actorName);

		if(!nameOK)
		{
			Tools_Timed_Message_System::AddMessage(0xff0000,"That name is already used");
			return 0;
		}
	}
	
	
	Tools_Mode_Cutscene::s_pxThis->RefreshPlayback();

	Asura_Cutscene_Actor* actor = AllocateActor(actorType,actorName);

	Asura_Assert(actor,"");
	actor->m_uGuid = uGuid;

	Cutscene->AddActor(actor);
	//in case a camera was created
	Cutscene->InvalidateNumberOfCameras();


    // Sort the new array. Actors can be dependent on others, eg. cameras on
    // camera targets, so there may be ordering issues.
	SortActors();

	//create an object for the actor
	Tools_CutsceneActor_Object* actorObject = AllocateToolsActor(actor);
	actorObject->Initialise();
	if(CutsceneUndo) CutsceneUndo->Log_Actor_Created(actorObject);


	//set its position
	actorObject->Position = *Asura_Camera::GetPosition();
	actorObject->Orientation = *Asura_Camera::GetOrientation();
	actorObject->SnapToLevel();

	if(bCreateTrack)
	{
		//create the actor's first track
		Tools_Object_System::SelectObject(actorObject);
		Tools_CutsceneActor_Object::CreateTrack();
		//reselect the actor , since the track will have been selected upon creation
		Tools_Object_System::SelectObject(actorObject);
	}
	
	Tools_SaveAsura::SetCutscenesModified();

	return actorObject;
}


void Tools_Cutscene_Object::RemoveActorFromCutscene(Asura_Cutscene_Actor* actor)
{
	//find the actor in the array
	int i;
	for(i=0;i<Cutscene->NumberOfActors;i++)
	{
		if(Cutscene->Actors[i] == actor)
		{
			break;
		}
	}
	Asura_Assert(i<Cutscene->NumberOfActors,"");

	//shuffle remaining down
	for(;i<Cutscene->NumberOfActors-1;i++)
	{
		Cutscene->Actors[i] = Cutscene->Actors[i+1];
	}

	//and reduce the number of actors
	//(won't bother resizing the array... it wil get sorted out when the cutscene is deleted)
	Cutscene->NumberOfActors--;

	Cutscene->InvalidateNumberOfCameras();


	//check for any events that are using this actor
    // (MH - 20.06.2005) changed this to destroy the events rather than set target actor to NULL.
	List<Tools_Cutscene_Event*> xActorEvents;
	for(LIF<Tools_Cutscene_Event*> elif(&EventList);!elif.done();elif.next())
	{
		if(elif()->Event->m_pxTargetActor == actor)
		{
			xActorEvents.add_entry(elif());
		}
	}
	//destroy all the objects in the list
	while(xActorEvents.size())
	{
		//get the next object to destroy
		Tools_Cutscene_Event* pxEvent = xActorEvents.first_entry();
		xActorEvents.delete_first_entry();
		// check it's still in the list (events could delete other events)
		if (!EventList.contains(pxEvent))
		{
			continue;
		}
		// destroy the event
		pxEvent->Destroy();
	}

	Tools_SaveAsura::SetCutscenesModified();
}

void Tools_Cutscene_Object::HandleActorNameChange(Asura_Cutscene_Actor* actor)
{
	//update the name stored with any events that are using this actor
	for(LIF<Tools_Cutscene_Event*> elif(&EventList);!elif.done();elif.next())
	{
		if(elif()->Event->m_pxTargetActor == actor)
		{
			elif()->SetTargetActor( actor );
		}
	}

	//re-select the current event , so that its overlay gets updated
	SelectEvent(SelectedEvent);
}

void Tools_Cutscene_Object::AddEventsToSelectionOverlay(ListSelection_Overlay& overlay)
{
	overlay.AddItem("Animation Change",ACET_AnimationChange);
	overlay.AddItem("Secondary Animation",ACET_SecondaryAnimation);
	overlay.AddItem("Texture Animation",ACET_TextureAnimation);
	overlay.AddItem("Time Scale",ACET_TimeScale);
	overlay.AddItem("Field Of View",ACET_FieldOfView);
	overlay.AddItem("Trigger",ACET_Trigger);
	overlay.AddItem("LoopBack",ACET_LoopBack);
	overlay.AddItem("Activate Attachment",ACET_ActivateAttachment);
	overlay.AddItem("Deactivate Attachment",ACET_DeactivateAttachment);
	overlay.AddItem("Reactivate Attachment",ACET_ReactivateAttachment);
	overlay.AddItem("Attachment Control",ACET_AttachmentControl);
	overlay.AddItem("Beam",ACET_Beam);
	overlay.AddItem("Fade",ACET_Fade);
	overlay.AddItem("Subtitle",ACET_Subtitle);
	overlay.AddItem("Tools Test Sound",ACET_Sound);
	overlay.AddItem("Change Camera",ACET_ChangeCamera);
	overlay.AddItem("Change Camera Target",ACET_ChangeCameraTarget);
	overlay.AddItem("Hide",ACET_Hide);
	overlay.AddItem("Unhide",ACET_Unhide);
	overlay.AddItem("Change Camera Shake", ACET_ChangeCameraShake);
	overlay.AddItem("Set Actor Alpha", ACET_SetAlpha);
	overlay.AddItem("Change Player Camera Control", ACET_ChangePlayerCameraControl);
#if defined(ASURA_USE_GUI_MENU_SYSTEM)
	overlay.AddItem("GUI Menu", ACET_GUIMenu);
	overlay.AddItem("Console Variables", ACET_ConsoleVar);
#endif	//defined(ASURA_USE_GUI_MENU_SYSTEM)
#ifdef ASURA_USE_FACIAL_ANIMATION_SYSTEM
	overlay.AddItem("Facial Animation", ACET_FacialAnimation);
#endif
	overlay.AddItem("Mixer Preset", ACET_MixerPreset);
#if defined ASURA_USE_HDR
	overlay.AddItem("HDR Settings", ACET_HDRSettings);
#endif

}

Asura_Cutscene_Event* Tools_Cutscene_Object::AllocateAsuraEvent(int eventType)
{
	return Asura_Cutscene_Event::AllocateEvent(eventType,Cutscene);
}

Tools_Cutscene_Event* Tools_Cutscene_Object::AllocateToolsEvent(Asura_Cutscene_Event* event,int eventID)
{
	switch(event->m_iType)
	{
		case ACET_AnimationChange :
			return new Tools_Cutscene_Event_AnimationChange(event,this,eventID);
			break;

		case ACET_SecondaryAnimation :
			return new Tools_Cutscene_Event_SecondaryAnimation(event,this,eventID);
			break;

		case ACET_TextureAnimation :
			return new Tools_Cutscene_Event_TextureAnimation(event,this,eventID);
			break;

		case ACET_TimeScale :
			return new Tools_Cutscene_Event_TimeScale(event,this,eventID);
			break;

		case ACET_FieldOfView :
			return new Tools_Cutscene_Event_FieldOfView(event,this,eventID);
			break;

		case ACET_Trigger :
			return new Tools_Cutscene_Event_Trigger(event,this,eventID);
			break;

        case ACET_LoopBack:
            return new Tools_Cutscene_Event_LoopBack(event,this,eventID);
            break;

		case ACET_ActivateAttachment :
		case ACET_DeactivateAttachment :
		case ACET_ReactivateAttachment:
			return new Tools_Cutscene_Event_Attachment(event,this,eventID);
			break;

		case ACET_AttachmentControl:
			return new Tools_Cutscene_Event_AttachmentControl(event,this,eventID);
			break;


		case ACET_Beam : 
			return new Tools_Cutscene_Event_Beam(event,this,eventID);
			break;

		case ACET_Fade : 
			return new Tools_Cutscene_Event_Fade(event,this,eventID);
			break;

		case ACET_TemplateWrapper :
			return new Tools_Cutscene_Event_TemplateWrapper(event,this,eventID);

		case ACET_Subtitle :
			return new Tools_Cutscene_Event_Subtitle(event,this,eventID);

		case ACET_Sound :
			return new Tools_Cutscene_Event_Sound(event,this,eventID);

		case ACET_ChangeCamera :
			return new Tools_Cutscene_Event_ChangeCamera(event,this,eventID);

		case ACET_ChangeCameraTarget :
			return new Tools_Cutscene_Event_ChangeCameraTarget(event,this,eventID);

		case ACET_Hide:
			return new Tools_Cutscene_Event_Hide(event, this, eventID);

		case ACET_Unhide:
			return new Tools_Cutscene_Event_Unhide(event, this, eventID);

		case ACET_ChangeCameraShake:
			return new Tools_Cutscene_Event_ChangeCameraShake(event,this,eventID);

		case ACET_SetAlpha:
			return new Tools_Cutscene_Event_SetAlpha(event, this, eventID);

		case ACET_ChangePlayerCameraControl:
			return new Tools_Cutscene_Event_ChangePlayerCameraControl(event, this, eventID);
     
#if defined(ASURA_USE_GUI_MENU_SYSTEM)
		case ACET_GUIMenu :
			 return new Tools_Cutscene_Event_GUIMenu(event, this, eventID);

		case ACET_ConsoleVar :
			 return new Tools_Cutscene_Event_ConsoleVar(event, this, eventID);
#endif	//defined(ASURA_USE_GUI_MENU_SYSTEM)

#ifdef ASURA_USE_FACIAL_ANIMATION_SYSTEM
		case ACET_FacialAnimation :
			return new Tools_Cutscene_Event_FacialAnimation(event,this,eventID);
#endif
		case ACET_MixerPreset :
			return new Tools_Cutscene_Event_MixerPreset(event,this,eventID);

#if defined ASURA_USE_HDR
		case ACET_HDRSettings:
			return new Tools_Cutscene_Event_HDRSettings(event, this, eventID);
#endif

		default :
			Asura_Assert(0,"Unrecognised event type");
	}
	return 0;
}

Tools_Cutscene_Event* Tools_Cutscene_Object::CreateCutsceneEvent(bool bShowUI,int eventType/*=-1*/,int eventID/*=-1*/,Asura_Cutscene_Event* pxEventToCopy /*= 0*/, Asura_Cutscene_Actor* pxTargetActor /*=0*/, float fTime /*= -1.0f*/)
{
	if(pxEventToCopy)
	{
		Asura_Assert(pxEventToCopy->m_pxCutscene == Cutscene,"Not expecting to copy an event from another cutscene");//this may be okay ,but would have to check...
		eventType = pxEventToCopy->GetType();
	}
	if(bShowUI && eventType<0)
	{
		//first need to choose the type of event
		ListSelection_Overlay overlay("Select Event Type :");
		AddEventsToSelectionOverlay(overlay);
		Asura_Assert(overlay.GetNumItems(),"");

		if(!overlay.DoModal()) return 0;
		eventType = overlay.GetSelectionData();
	}

	Tools_Mode_Cutscene::s_pxThis->RefreshPlayback();

	Asura_Cutscene_Event* event = NULL;

	//create the new event
	event = AllocateAsuraEvent(eventType);
	Asura_Assert(event,"");

	Asura_ResourcePushType(ASURA_RESOURCE_CUTSCENE);
	{
		//enlarge the array of events
		Cutscene->NumberOfEvents++;
		Asura_Cutscene_Event** NewEvents;
		Asura_ResourceAllocateArray(NewEvents, Cutscene->NumberOfEvents);
		for(int i=0;i<Cutscene->NumberOfEvents-1;i++)
		{
			NewEvents[i] = Cutscene->Events[i];
		}

		//add on the new event
		NewEvents[Cutscene->NumberOfEvents-1] = event;

		//replace the old array
		Asura_ResourceDeallocateArray(Cutscene->Events);
		Cutscene->Events = NewEvents;
	}
	Asura_ResourcePopType();

	//choose a time for the new event
	if(pxEventToCopy)
	{
		//get time , and everything else from the event being copied
		event->Copy(pxEventToCopy);
	}
	else if(Tools_Mode_Cutscene::s_pxThis->CutscenePlayback_Active)
	{
		//use the current time in the cutscene
		event->m_fTriggerTime = Cutscene->Timer;
	}
	else if(SelectedEvent && Asura_Overlay_System::DoesOverlayExist(TOOLS_OVERLAY_CUTSCENE_EVENT))
	{
		//use the time of the event selected
		event->m_fTriggerTime = SelectedEvent->Event->m_fTriggerTime;
	}
	else if(Cutscene->NumberOfEvents>=2)
	{
		//use the time of the previous event
		event->m_fTriggerTime = Cutscene->Events[Cutscene->NumberOfEvents-2]->m_fTriggerTime;
	}
	if(fTime >= 0.0f)
	{
		event->m_fTriggerTime = fTime;
	}
	if(pxTargetActor)
	{
		event->m_pxTargetActor = pxTargetActor;
		event->m_xTargetActorName = pxTargetActor->ActorName;
	}

	//create the tools event
	Tools_Cutscene_Event* toolsEvent = AllocateToolsEvent(event,eventID);
	toolsEvent->UpdateBackupVariables();
	SortEvents();
	UpdateEventIndeces();


	if(CutsceneUndo) CutsceneUndo->Log_Event_Created(toolsEvent);
		
	SelectEvent(toolsEvent);

	if(bShowUI)
	{
		//Prompt user for required event data
		toolsEvent->AskForRequiredData();
	}
	if(CutsceneUndo) CutsceneUndo->Log_Event_Changed(toolsEvent);
	
	Tools_SaveAsura::SetCutscenesModified();

	if(pxEventToCopy)
	{
		Tools_Timed_Message_System::AddMessage(0xffffff,"Created copy of event.");
	}

	#ifdef ASURA_TOOLS_USE_NEW_MESSAGE_EDITOR
	//Invalidate the message editor in case a trigger event was created
	Tools_MessageEditor::UpdateMessageEditorTable();
	#endif
 
	return toolsEvent;
}

void Tools_Cutscene_Object::RemoveEventFromCutscene(Asura_Cutscene_Event* event)
{
	//find the event in the array
	int i;
	for(i=0;i<Cutscene->NumberOfEvents;i++)
	{
		if(Cutscene->Events[i] == event)
		{
			break;
		}
	}
	Asura_Assert(i<Cutscene->NumberOfEvents,"");

	//shuffle remaining down
	for(;i<Cutscene->NumberOfEvents-1;i++)
	{
		Cutscene->Events[i] = Cutscene->Events[i+1];
	}

	//and reduce the number of  events
	//(won't bother resizing the array... it wil get sorted out when the cutscene is deleted)
	Cutscene->NumberOfEvents--;

	Tools_SaveAsura::SetCutscenesModified();

	#ifdef ASURA_TOOLS_USE_NEW_MESSAGE_EDITOR
	//Invalidate the message editor in case a trigger event was removed
	Tools_MessageEditor::UpdateMessageEditorTable();
	#endif

}

void Tools_Cutscene_Object::UpdateTracks()
{
	for(LIF<Tools_CutsceneActor_Object*> alif(&ActorObjects);!alif.done();alif.next())
	{
		alif()->UpdateTracks();
	}
}

void Tools_Cutscene_Object::PerframeUpdateWhilePlaying()
{
	//move the camera and focus objects to match the positions in the cutscene
	for(LIF<Tools_CutsceneActor_Object*> alif(&ActorObjects);!alif.done();alif.next())
	{
//		if (alif()->Actor->Type != ACAT_Camera && 
//            alif()->Actor->Type != ACAT_CameraTarget) 
		if ( (!alif()->Actor->IsCamera()) && 
             (!alif()->Actor->IsCameraTarget()) )
        {
            continue;
        }

		alif()->Position = alif()->Actor->Position;
		alif()->Orientation = alif()->Actor->Orientation;
	}
}

void Tools_Cutscene_Object::UpdateAfterCutscene()
{
	//move the camera and focus objects back to the start of their tracks
	for(LIF<Tools_CutsceneActor_Object*> alif(&ActorObjects);!alif.done();alif.next())
	{
		alif()->UpdatePositionFromTrack();
	}

}

bool Tools_Cutscene_Object::EditFirstEventForActor(Tools_CutsceneActor_Object* pxToolsActor, int iEventType /*=-1*/)
{
	Asura_Cutscene_Actor* pxAsuraActor = pxToolsActor ? pxToolsActor->Actor : 0;

	//find the first event targeting the chosen actor
	for(LIF<Tools_Cutscene_Event*> xEventLIF(&EventList);!xEventLIF.done();xEventLIF.next())
	{
		if(iEventType != -1)
		{
			//make sure the event type matches
			if(xEventLIF()->Event->m_iType != iEventType) continue;
		}
		
		if(xEventLIF()->Event->m_pxTargetActor == pxAsuraActor)
		{
			SelectEvent(xEventLIF());
			return true;
		}
	}

	if(iEventType == -1)
	{
		Tools_Timed_Message_System::AddMessage(0xff0000,"This actor doesn't have any events.");
	}
	return false;
}

void Tools_Cutscene_Object::SelectEventByIndex(int iIndex)
{
	Asura_Assert(iIndex>=0 && iIndex<EventList.size(),"");

	SelectEvent(EventList[iIndex]);
}

void Tools_Cutscene_Object::SelectEvent(Tools_Cutscene_Event* event)
{

	if(SelectedEvent)
	{
		SelectedEvent->OnDeselection();
	}	

	SelectedEvent = event;

	if(SelectedEvent)
	{
		SelectedEvent->OnSelection();
		SelectedEventIndex = (float)SelectedEvent->Index;
	}	
	SelectedEventIndexChanged = false;

}

bool Tools_Cutscene_Object::ChooseEvent(const char* message,Tools_Cutscene_Event*& outputEvent,ToolsChooseEventFilterFn fnFilter/*=0*/,int iData/*=0*/)
{
	ListSelection_Overlay overlay(message);

	//add all theve events to the overlay
	for(LIF<Tools_Cutscene_Event*> elif(&EventList);!elif.done();elif.next())
	{
		if(fnFilter && !fnFilter(elif(),iData)) continue;

		char description[200];
		sprintf(description,"%d %s",elif()->Index,elif()->GetDescription());
	
		overlay.AddItem(description,(int)elif());
	}

	//add the option to choose nothing
	overlay.AddItem("(None)",0);

	//show the overlay
	if(!overlay.DoModal()) return false;
	
	outputEvent = (Tools_Cutscene_Event*) overlay.GetSelectionData();

	return true;
}

void Tools_Cutscene_Object::UpdateEventIndeces()
{
	int i = 0;
	for(LIF<Tools_Cutscene_Event*> elif(&EventList);!elif.done();elif.next(),i++)
	{
		elif()->Index = i;
	}

	if(SelectedEvent)
	{
		SelectedEventIndex = (float) SelectedEvent->Index;
	}

}

Tools_CutsceneActor_Object* Tools_Cutscene_Object::GetActor(const char* actorName)
{
	if(!actorName) return 0;

	for(LIF<Tools_CutsceneActor_Object*> alif(&ActorObjects);!alif.done();alif.next())
	{
		Asura_Assert(alif()->Actor->ActorName.GetString(), "Cutscene actor has NULL name");
		if (alif()->Actor->ActorName.GetString() &&
			!_stricmp(actorName,alif()->Actor->ActorName.GetString()))
		{
			return alif();
		}
	}
	return 0;
}



void Tools_Cutscene_Object::SortEvents()
{
	m_bEventSortRequired = false;
	bool loopAgain = false;
	bool orderChanged = false;
	int loops = 0;
	//sort the array of events by time
	do
	{
		loopAgain = false;
		for(int i=0;i<Cutscene->NumberOfEvents-loops-1;i++)
		{
			if(Cutscene->Events[i]->m_fTriggerTime > Cutscene->Events[i+1]->m_fTriggerTime)
			{
				Asura_Cutscene_Event* temp = Cutscene->Events[i];
				Cutscene->Events[i] = Cutscene->Events[i+1];
				Cutscene->Events[i+1] = temp;

				loopAgain = true;
				orderChanged = true;
			}
		}	
		loops++;
	}while(loopAgain);

	if(orderChanged)
	{
		//need to get the list to match the array
		//a pretty rubbish method of doing this really , but with some luck it shouldn't matter
		List<Tools_Cutscene_Event*>	listCopy = EventList;
		while(EventList.size()) EventList.delete_first_entry();

		for(int i=0;i<Cutscene->NumberOfEvents;i++)
		{
			for(LIF<Tools_Cutscene_Event*> elif(&listCopy);!elif.done();elif.next())
			{
				if(elif()->Event == Cutscene->Events[i])
				{
					EventList.add_entry(elif());
					elif.delete_current();
					break;
				}
			}
		}
		Asura_Assert(!listCopy.size(),"");

		UpdateEventIndeces();
		UpdateParentEvents();

		Tools_Cutscene_Event::UpdateEventListStrings();
	}
}

void Tools_Cutscene_Object::UpdateEventsAfterUndo()
{
	//need to make sure the actor pointers are up to data
	for(LIF<Tools_Cutscene_Event*> elif(&EventList);!elif.done();elif.next())
	{
		Tools_CutsceneActor_Object* actorObject = GetActorObject(elif()->Event->m_xTargetActorName);
		elif()->SetTargetActor( actorObject ? actorObject->Actor : NULL );
	}

	//and make sure the events are sorted properly
	SortEvents();
	UpdateParentEvents();

	if(SelectedEvent)
	{
		//update the overlay for the selected event
		SelectEvent(SelectedEvent);
	}

}

void Tools_Cutscene_Object::UpdateParentEvents()
{
	//update parent event indeces
	for(LIF<Tools_Cutscene_Event*> elif(&EventList);!elif.done();elif.next())
	{
		elif()->UpdateEventIndecesFromIDs();
	}
}

void Tools_Cutscene_Object::UpdateEventsAfterTimeChange(List<Tools_Cutscene_Event*> alteredEventList)
{
	List<Tools_Cutscene_Event*> unalteredList = EventList;
	for(LIF<Tools_Cutscene_Event*> alif(&alteredEventList);!alif.done();alif.next())
	{
		unalteredList.delete_entry(alif());
	}

	//See if any events that weren't altered have an altered event as a parent.
	//If so , adjust its time , and add to the list of altered events

	for(LIF<Tools_Cutscene_Event*> alif(&alteredEventList);!alif.done();alif.next())
	{
		for(LIF<Tools_Cutscene_Event*> ulif(&unalteredList);!ulif.done();)
		{
			if(ulif()->ParentEventID == alif()->EventID)
			{
				float newTime = alif()->Event->m_fTriggerTime + ulif()->RelativeTime;
				if(newTime<0)newTime = 0;
				ulif()->Event->m_fTriggerTime = newTime;
				if(CutsceneUndo) CutsceneUndo->Log_Event_Changed(ulif());

				alteredEventList.add_entry(ulif());
				ulif.delete_current();
			}
			else
			{
				ulif.next();
			}
		}
	}
}


const char* Tools_Cutscene_Object::GetCutsceneDirectory()
{
	if(!Cutscene->CutsceneDirectory.GetLength()) return 0;
	return Cutscene->CutsceneDirectory.GetString();
}

void Tools_Cutscene_Object::EditCutsceneDirectory()
{
	Tools_FileSystem_SelectFolderInfo info;
	//info.StartDirectory = "Cutscenes";
	char startDirectory[MAX_PATH];
	GetCurrentDirectory(MAX_PATH,startDirectory);
	strcat(startDirectory,"\\Cutscenes");

	info.StartDirectory = startDirectory;
	info.DisplayText = "Select the folder containing additional files for this cutscene";

	if(Tools_FileSystem::SelectFolder(info))
	{
		Asura_Assert(strlen(info.SelectedFolder)>=strlen(startDirectory),"");
		//get a relative directory
		char* directory = &info.SelectedFolder[strlen(startDirectory)];
		if(directory[0]=='\\' || directory[0] == '/') directory++;

		Cutscene->CutsceneDirectory = directory;;
	}
	else
	{
		if(info.Error == Tools_FileSystem_SelectFolderInfo::Error_DirectoryDoesntExist)
		{
			Tools_Timed_Message_System::AddMessage(0xff0000,"Failed to find cutscenes folder");
		}
	}
}



bool Tools_Cutscene_Object::Rename()
{
	GetString_Overlay xOverlay("Cutscene Name :", Cutscene->CutsceneName.GetString());
	if(!xOverlay.DoModal()) return false;

	if(Asura_Cutscene_System::GetCutscene(xOverlay.GetString()))
	{
		Tools_Timed_Message_System::AddMessage(0xff0000,"A cutscene already exists with that name");
		return false;
	}

	if(Asura_Cutscene_System::RenameCutscene(Cutscene->CutsceneName.GetString(),xOverlay.GetString()))
	{
		return true;
	}

	return false;
}


void Tools_Cutscene_Object::AddContextMenuItems(class Tools_ContextMenu& xMenu, const class Tools_ContextMenu_Context& xContext)
{
	Tools_Object::AddContextMenuItems(xMenu, xContext);

	if(this==Tools_Mode_Cutscene::CurrentCutscene)
	{
		Tools_ContextMenu_Item* pxItem;

		Tools_ContextMenu_Section* pxSection = xMenu.GetSection(TOOLS_CONTEXTMENU_SECTION_SELECTION);
		pxSection->AddSeparator();

		//Add option for selecting/deselecting everything in this cutscene
		u_int uMode;
		const char* szText;
		if(xContext.m_bShiftHeld)
		{
			szText = "Deselect cutscene";
			uMode = 0;
		}
		else if(xContext.m_bControlHeld)
		{
			szText = "Select cutscene";
			uMode = 1;
		}
		else
		{
			szText = "Select cutscene";
			uMode = 2;
		}
		
		pxItem = new Tools_ContextMenu_Item_Function(szText, ContextMenu_SelectCutscene, this, uMode);
		pxItem->SetID("Select cutscene");
		pxItem->SetHelpText("Selects/deselects all actors and tracks in this cutscene");
		pxSection->AddItem(pxItem);
		
	}
}

void Tools_Cutscene_Object::ContextMenu_SelectCutscene(void* pObject, u_int uMode)
{
	Tools_Object* pxObject = (Tools_Object*) pObject;

	Asura_Assert(pxObject && pxObject->Type==TOT_Cutscene,"");
	Tools_Cutscene_Object* pxCutsceneObject = (Tools_Cutscene_Object*) pxObject;

	List<Tools_Object*> xSelectionList;
	
	//get all objects used by the actors
	for(LIF<Tools_CutsceneActor_Object*> xActorLIF(&pxCutsceneObject->ActorObjects);!xActorLIF.done();xActorLIF.next())
	{
		xActorLIF()->ListObjectsForActor(xSelectionList);
	}
	//add the cutscene object itself
	xSelectionList.add_entry(pxCutsceneObject);

	//And (de)select them
	if(uMode == 0)
	{
		Tools_Object_System::DeselectObjects(xSelectionList);
	}
	else
	{
		Tools_Object_System::SelectObjects(xSelectionList, uMode == 1);
	}
}



void Tools_Cutscene_Object::EditMessageBlock()
{
	Asura_Assert(Tools_Mode_Cutscene::CurrentCutscene,"");

	#ifdef ASURA_TOOLS_USE_NEW_MESSAGE_EDITOR
	Tools_Object_System::SelectObject(Tools_Mode_Cutscene::CurrentCutscene);
	Tools_MessageEditor::StartMessageEditingMode(&Tools_Mode_Cutscene::CurrentCutscene->Cutscene->m_usEndCutsceneMessageBlock);
//	Tools_Object_System::EditMessageBlock("End Cutscene Message",&Tools_Mode_Cutscene::CurrentCutscene->Cutscene->m_usEndCutsceneMessageBlock,ASURA_STATICMESSAGE_CUTSCENESET);
	#else
	Tools_Object_System::EditMessageBlock("End Cutscene Message",&Tools_Mode_Cutscene::CurrentCutscene->Cutscene->m_usEndCutsceneMessageBlock,ASURA_STATICMESSAGE_CUTSCENESET);
	#endif
	Tools_SaveAsura::SetCutscenesModified();

}

// pxCutscene is source actor's cutscene
// pxActorToCopy is source actor
void Tools_Cutscene_Object::CopyActorEvents(const Asura_Cutscene* pxCutscene, 
											const Asura_Cutscene_Actor* pxActorToCopy, 
											Asura_Cutscene_Actor* pxTargetActor,
											List<Tools_Cutscene_Event*>& xListOfCopiedToolsEvents)
{
	//Build two lists of events to store equivalent events between the cutscenes.
	//If the actor is being copied from this cutscene , then add all the events to both lists
	Asura_Cutscene_Event** ppxFromEventArray = new Asura_Cutscene_Event*[Cutscene->NumberOfEvents+pxCutscene->NumberOfEvents];
	Asura_Cutscene_Event** ppxToEventArray = new Asura_Cutscene_Event*[Cutscene->NumberOfEvents+pxCutscene->NumberOfEvents];
	int iNumMatchingEvents = 0;

	if(Cutscene == pxCutscene)
	{
		for(int i=0;i<Cutscene->NumberOfEvents;i++)
		{
			ppxFromEventArray[iNumMatchingEvents] = Cutscene->Events[i];
			ppxToEventArray[iNumMatchingEvents++] = Cutscene->Events[i];
		}
	}


	//Get a list of events to copy
	List<Asura_Cutscene_Event*> xEventList;
	for(int i=0;i<pxCutscene->NumberOfEvents;i++)
	{
		if(pxCutscene->Events[i]->m_pxTargetActor != pxActorToCopy) continue;
		xEventList.add_entry(pxCutscene->Events[i]);
	}

	List<Asura_Cutscene_Event*> xEventCopyList;

	//Allocate the copies of the events
	LIF<Asura_Cutscene_Event*> xEventLIF(&xEventList);
	LIF<Asura_Cutscene_Event*> xEventCopyLIF(&xEventCopyList);
	for(xEventLIF.restart();!xEventLIF.done();xEventLIF.next())
	{

		Tools_Cutscene_Event* pxToolsEvent = CreateCutsceneEvent(false,xEventLIF()->m_iType);
		xListOfCopiedToolsEvents.add_entry(pxToolsEvent);
		Asura_Cutscene_Event* pxEvent = pxToolsEvent->Event;
		xEventCopyList.add_entry(pxEvent);

		//update the array of equivalent events
		bool bAddedToMatchingEvents = false;
		for(int i=0;i<iNumMatchingEvents;i++)
		{
			if(ppxFromEventArray[i]==xEventLIF())
			{
				ppxToEventArray[i] = pxEvent;
				bAddedToMatchingEvents = true;
				break;
			}
		}
		if(!bAddedToMatchingEvents)
		{
			ppxFromEventArray[iNumMatchingEvents] = xEventLIF();
			ppxToEventArray[iNumMatchingEvents++] = pxEvent;
		}

	}
	//and do the copying
	for(xEventLIF.restart(),xEventCopyLIF.restart();!xEventLIF.done();xEventLIF.next(),xEventCopyLIF.next())
	{
		Asura_Cutscene_Event* pxEvent = xEventCopyLIF();
		
		pxEvent->Copy(xEventLIF());

		//update stored event indeces
		List<int*> xIndexList;
		pxEvent->GetEventIndexPointers(xIndexList);

		for(LIF<int*> xIndexLIF(&xIndexList);!xIndexLIF.done();xIndexLIF.next())
		{
			if(*xIndexLIF()==-1) continue;
			Asura_Cutscene_Event* pxOriginalEvent = pxCutscene->Events[*xIndexLIF()];

			//set to -1 now , in case no equivalent event is found
			*xIndexLIF() = -1;

			//search for the event in the list of matching events
			for(int i=0;i<iNumMatchingEvents;i++)
			{
				if(ppxFromEventArray[i] == pxOriginalEvent)
				{
					//okay , find the index of the matching event
					for(int j=0;j<Cutscene->NumberOfEvents;j++)
					{
						if(ppxToEventArray[i] == Cutscene->Events[j])
						{
							*xIndexLIF() = j;
							break;
						}
					}
					break;
				}
			}
		}
	}

    // (MH - 17.06.2005) also update target actor for the copied events.
	for (LIF<Tools_Cutscene_Event*> elif(&xListOfCopiedToolsEvents);!elif.done();elif.next())
	{
		elif()->SetTargetActor(pxTargetActor);
	}

	//update the event IDs stored in the tools events
	for(LIF<Tools_Cutscene_Event*> elif(&EventList);!elif.done();elif.next())
	{
		elif()->UpdateEventIDsFromIndeces();
	}
	//and re-sort
	SortEvents();

	delete [] ppxFromEventArray;
	delete [] ppxToEventArray;
}

void Tools_Cutscene_Object::CopyActor()
{
	//First need to select the cutscene to copy from
	//Get a list of all cutscenes of the same type as this
	ListSelection_Overlay xCutsceneListOverlay("Select cutscene to copy from");
	xCutsceneListOverlay.AddItem("*This Cutscene*",(int)Cutscene);
	
	for(int i=0;i<Asura_Cutscene_System::GetNumberOfCutscenes();i++)
	{
		Asura_Cutscene* pxCutscene = Asura_Cutscene_System::GetCutsceneByIndex(i);
		if(pxCutscene == Cutscene) continue;
		if(pxCutscene->GetCutsceneType() == Cutscene->GetCutsceneType())
		{
			xCutsceneListOverlay.AddItem(pxCutscene->CutsceneName.GetString(),(int)pxCutscene);
		}
	}

	if(!xCutsceneListOverlay.DoModal()) return;

	Asura_Cutscene* pxCutscene = (Asura_Cutscene*) xCutsceneListOverlay.GetSelectionData();
	if(!pxCutscene->NumberOfActors)
	{
		Tools_Timed_Message_System::AddMessage(0xff0000,"%s doesn't contain any actors",pxCutscene->CutsceneName.GetString());
		return;
	}
	

	//Next select the actor from the cutscene
	ListSelection_Overlay xActorListOverlay("Select actor to copy");
	for(int i=0;i<pxCutscene->NumberOfActors;i++)
	{
		xActorListOverlay.AddItem(pxCutscene->Actors[i]->ActorName.GetString(),(int)pxCutscene->Actors[i]);
	}

	if(!xActorListOverlay.DoModal()) return;

	Asura_Cutscene_Actor* pxActorToCopy =  (Asura_Cutscene_Actor*) xActorListOverlay.GetSelectionData();

	//Determine whether this should overwrite an existing actor
	Tools_CutsceneActor_Object* pxActorToOverwrite = 0;

	if(ShowYesNoOverlay("Copy over an existing actor?.\nIf this is done then that actor's events and tracks will be preserved , but any other properties will be updated."))
	{
		//populate overlay with actors of the same type
		ListSelection_Overlay xActorOverwriteOverlay("Select actor to overwrite.");
		for(LIF<Tools_CutsceneActor_Object*> xActorLIF(&ActorObjects);!xActorLIF.done();xActorLIF.next())
		{
			if(xActorLIF()->Actor == pxActorToCopy) continue;//actor can't copy over itself

			if(xActorLIF()->Actor->Type == pxActorToCopy->Type)
			{
				xActorOverwriteOverlay.AddItem(xActorLIF()->GetName(),(int)xActorLIF());
			}		
		}

		if(!xActorOverwriteOverlay.GetNumItems())
		{
			ShowMessageOverlay("This cutscene doesn't contain any existing actors of the same type.");
			return;
		}

		if(!xActorOverwriteOverlay.DoModal()) return;

		pxActorToOverwrite = (Tools_CutsceneActor_Object*) xActorOverwriteOverlay.GetSelectionData();
	}
	


	//Choose a name for the new actor.
	//Go with the name of the copied actor , unless that is already in use , in which case ask

	Asura_String xName = pxActorToCopy->ActorName;
	if(pxActorToOverwrite)
	{
		//name of existing actor will be kept
		xName = pxActorToOverwrite->GetName();
	}
	else
	{
		bool bOkay;
		do
		{
			bOkay = true;
			if(!xName.GetLength()) bOkay = false;
			for(int i=0;i<Cutscene->NumberOfActors;i++)
			{
				if(Cutscene->Actors[i]->ActorName == xName)
				{
					bOkay = false;
					break;
				}
			}
			if(!bOkay)
			{
				GetString_Overlay overlay("Choose name for actor :",xName.GetString());
				if(!overlay.DoModal()) return;
				xName = overlay.GetString();
			}

		}while(!bOkay);
	}

	CopyActor(pxCutscene, pxActorToCopy, pxActorToOverwrite, xName.GetString());


	//reselect this cutscene for editing , to ensure that all the tools objects are created
	Tools_Mode_Cutscene::s_pxThis->EditCutscene(0);		
	Tools_Mode_Cutscene::s_pxThis->EditCutscene(this);		
}

void Tools_Cutscene_Object::DuplicateSelectedActors()
{
	//find all the selected actors
	List<Tools_Object*> xObjectList;
	Tools_Object_System::ListSelectedObjectsOfType(TOT_CutsceneActor, xObjectList);

	if(!xObjectList.size())
	{
		Tools_Timed_Message_System::AddMessage(0xffffff,"No actors selected for duplication.");
		return;
	}

	if(!ShowYesNoOverlay("Duplicate the %d selected actor(s)?",xObjectList.size())) return;

	if(xObjectList.contains(Tools_Object_System::SelectedObject))
	{
		//move the primary selection to the end of the list , so that the copy of it ends up
		//as the primary selection once we are done.
		xObjectList.delete_entry(Tools_Object_System::SelectedObject);
		xObjectList.add_entry(Tools_Object_System::SelectedObject);
	}

	List<Asura_Cutscene_Actor*> xNewActorList;

	//deal with each actor
	for(LIF<Tools_Object*> xObjectLIF(&xObjectList); !xObjectLIF.done(); xObjectLIF.next())
	{
		Tools_CutsceneActor_Object* pxToolsActor = (Tools_CutsceneActor_Object*) xObjectLIF();
		const char* szSourceActorName = pxToolsActor->GetName();
		
		//Generate a name for the copy...
		//If there is a numerical postfix on the name , cut it off , so it can be incremented when making the new name
		char szPrefix[200];
		Asura_PrintfToBfr(szPrefix,"%s",sizeof(szPrefix),true,szSourceActorName);

		char* pcEnd = &szPrefix[strlen(szPrefix)-1];

		while(pcEnd>&szPrefix[0])
		{
			if(*pcEnd == ' ' || (*pcEnd >= '0' && *pcEnd <= '9'))
			{
				pcEnd--;
			}
			else
			{
				break;
			}
		}
		pcEnd++;

		//Get the number (if any) from the end
		int iPostfixNumber = 0;

		sscanf(pcEnd,"%d",&iPostfixNumber);
		*pcEnd = 0;

		char acNewName[300];
		bool bNameOkay = false;
		do
		{
			Asura_PrintfToBfr(acNewName,"%s %d",sizeof(acNewName),true,szPrefix,++iPostfixNumber);

			//make sure this name isn't already used
			bNameOkay = true;
			for(int i=0; i<Cutscene->NumberOfActors;i++)
			{
				if(!Cutscene->Actors[i]->ActorName.Compare(acNewName))
				{
					bNameOkay = false;
					break;
				}
			}
		}
		while(!bNameOkay);

		//do the copying
		Asura_Cutscene_Actor* pxActor = CopyActor(Cutscene, pxToolsActor->Actor, NULL, acNewName);
		if(pxActor)
		{
			xNewActorList.add_entry(pxActor);
		}
	}

	if(!xNewActorList.size()) return;

	//reselect this cutscene for editing , to ensure that all the tools objects are created
	Tools_Mode_Cutscene::s_pxThis->EditCutscene(0);		
	Tools_Mode_Cutscene::s_pxThis->EditCutscene(this);

	//find the tools objects for the new actors , and select them
	List<Tools_Object*> xObjectsToSelect;
	for(LIF<Asura_Cutscene_Actor*> xNewActorLIF(&xNewActorList); !xNewActorLIF.done(); xNewActorLIF.next())
	{
		Tools_Object* pxObject = GetActorObject(xNewActorLIF()->ActorName);
		if(pxObject)
		{
			xObjectsToSelect.add_entry(pxObject);
		}
	}

	Tools_Object_System::SelectObjects(xObjectsToSelect, false);

}

Asura_Cutscene_Actor* Tools_Cutscene_Object::CopyActor(Asura_Cutscene* pxCutsceneToCopy,Asura_Cutscene_Actor* pxActorToCopy,Tools_CutsceneActor_Object* pxActorToOverwrite,const char* szActorName)
{
	Asura_Guid uGuid = pxActorToOverwrite ? pxActorToOverwrite->GetGuid() : ASURA_GUID_UNREGISTERED;

	//create a new actor of the appropriate type
	//because copying is only safe over newly created actors ,we need to create a new actor
	//even if we are in theory overwriting an existing one.
	Tools_CutsceneActor_Object* pxToolsActor = CreateActor(pxActorToCopy->Type,szActorName,uGuid,false);	
	Asura_Cutscene_Actor* pxNewActor = pxToolsActor->Actor;


	//copy from the other actor
	pxNewActor->Copy(pxActorToCopy);
	pxNewActor->ActorName = szActorName;//copying will have overwritten the name

	if(pxActorToOverwrite)
	{
		Asura_ResourcePushType(ASURA_RESOURCE_CUTSCENE);
		{
			//need to grab the tracks back from the actor that was being overwritten

			for(int i=0;i<pxNewActor->NumberOfTracks;i++)
			{
				delete pxNewActor->Tracks[i];
			}
			Asura_ResourceDeallocateArray(pxNewActor->Tracks);
			pxNewActor->Tracks = 0;
			pxNewActor->NumberOfTracks = 0;
			pxNewActor->CopyTracks(pxActorToOverwrite->Actor);
		}
		Asura_ResourcePopType();
	}
	else
	{
		//give the copied tracks default names based on the name of the actor
		if(pxActorToCopy->ActorName.Compare(szActorName))
		{
			for(int i=0;i<pxNewActor->NumberOfTracks;i++)
			{
				Asura_Cutscene_Track* pxTrack = pxNewActor->Tracks[i];
				if(i)
				{
					char szName[200];
					sprintf(szName,"%s %d",szActorName, i);
					pxTrack->TrackName = szName;
				}
				else
				{
					pxTrack->TrackName = szActorName;
				}
			}
		}
	}
	
	//Event copying...
	if(pxActorToOverwrite)
	{
		//events get left alone in this case.. but need to update the target actor pointers
		Asura_Cutscene_Actor* pxOldActor = pxActorToOverwrite->Actor;
	
		for(int i=0;i<Cutscene->NumberOfEvents;i++)
		{
			if(Cutscene->Events[i]->m_pxTargetActor == pxOldActor)
			{
				Cutscene->Events[i]->m_pxTargetActor = pxNewActor;
			}
		}
	}
	else
	{
		List<Tools_Cutscene_Event*> xListOfCopiedToolsEvents;
		CopyActorEvents(pxCutsceneToCopy, pxActorToCopy, pxNewActor, xListOfCopiedToolsEvents);
	}


	//Check for actor attachments. If these exist ,then duplicates of those actors will
	//be needed as well.
	if(!pxActorToOverwrite)
	{
		if(pxNewActor->IsHierarchyActor())
		{
			Asura_Cutscene_Actor_Hierarchy* pxHierActor = (Asura_Cutscene_Actor_Hierarchy*) pxNewActor;
			for(int i=0;i<pxHierActor->m_iNumberOfAttachments;i++)
			{
				if(pxHierActor->m_ppxAttachments[i]->GetType() != AsuraCutsceneAttachment_Actor) continue;

				Asura_Cutscene_Attachment_Actor* pxAttachment = (Asura_Cutscene_Attachment_Actor*) pxHierActor->m_ppxAttachments[i];
				const char* szAttachedActorName = pxAttachment->GetActorName();

				//find the attached actor in the original cutscene
				Asura_Cutscene_Actor* pxAttachedActorToCopy = pxCutsceneToCopy->GetActorByName(szAttachedActorName);
				if(!pxAttachedActorToCopy) continue;

				//generate a name for the copied attachment
				char szNewName[200];
				Asura_PrintfToBfr(szNewName,"%s",sizeof(szNewName)-20,true,szAttachedActorName);
				int iPostfix = 1;

				while(Cutscene->GetActorByName(szNewName))
				{
					//strip off any postfix
					char* pcEnd = &szNewName[strlen(szNewName)-1];

					while(pcEnd>&szNewName[0])
					{
						if(*pcEnd == ' ' || (*pcEnd >= '0' && *pcEnd <= '9'))
						{
							pcEnd--;
						}
						else
						{
							break;
						}
					}
					pcEnd++;
					*pcEnd = 0;

					//add a new postfix
					sprintf(pcEnd," %d",iPostfix++);
				}
				//generate the copied actor
				CopyActor(pxCutsceneToCopy,pxAttachedActorToCopy,0,szNewName);

				//and update the name in the attachment
				pxAttachment->SetActorName(szNewName);
				
			}
		}			
	}

	
	if(pxActorToOverwrite)
	{
		//The newly created actor should now be an acceptable combination of the 2 actors.
		//Delete this one to allow the new one to take its place
		pxActorToOverwrite->Destroy();

	}
	return pxNewActor;
}


void Tools_Cutscene_Object::RecordWavFile()
{
	Asura_Assert(!Cutscene->Playing,"");


	//find the camera actor
	Asura_Cutscene_Actor* pxCamera = 0;
	for(int i=0;i<Cutscene->NumberOfActors;i++)
	{
		if(Cutscene->Actors[i]->Type ==  ACAT_Camera)
		{
			pxCamera = Cutscene->Actors[i];				
		}
	}
	if(!pxCamera)
	{
		Tools_Timed_Message_System::AddMessage(0xff0000,"Cutscene must have a camera actor to record wav file.");
		return;
	}

	//Get the name of the cutscene , and replace undesirable characters with underscores
	char szName[MAX_PATH];
	strcpy(szName,Cutscene->CutsceneName.GetString());

	char* pcPos = szName;
	while(*pcPos)
	{
		pcPos = pcPos + strcspn(pcPos,".\\/:*?\"<>|");
		if(*pcPos) *pcPos = '_';
	}

	//generate a filename from this
	char szFilename[MAX_PATH];
	char szLocalisedFilename[MAX_PATH];
	sprintf(szFilename,"Sounds\\Cutscene\\%s.wav",szName);

	Cutscene->LocaliseSoundName(szFilename,szLocalisedFilename,MAX_PATH);

	Tools_FileSystem::EnsureDirectoryExists(szFilename);



	Tools_WavWriter xWriter;
	if(!xWriter.StartOutput(szLocalisedFilename))
	{
		Tools_Timed_Message_System::AddMessage(0xff0000,"Failed to open wav file for writing.");
		return;
	}

	Cutscene->m_bRecordingWavFile = true;
	Cutscene->Initialise();

	if(Cutscene->CutsceneEndTime==0)
	{
		Tools_Timed_Message_System::AddMessage(0xff0000,"No end time has been set for this cutscene.");
		Cutscene->Finish();
		return;
	}

	Cutscene->Update(0);
	Cutscene->Render();
	float fPreviousTime = Cutscene->Timer;

	do
	{
		//add any newly started sounds
		for(int i=0;i<Cutscene->NumberOfEvents;i++)
		{
			Asura_Cutscene_Event* pxEvent = Cutscene->Events[i];
			//check for sound events that have just been triggered
			if(pxEvent->GetType() != ACET_Sound) continue;
			if(fPreviousTime<=pxEvent->m_fTriggerTime && pxEvent->HasBeenTriggered())
			{
				Asura_Cutscene_Event_Sound* pxEventSound = (Asura_Cutscene_Event_Sound*) pxEvent;
				if(pxEventSound->m_xSoundName.IsBlank()) continue;

				Tools_CutsceneActor_Object* pxActorObject = 0;
				if(!pxEventSound->m_xTargetActorName.IsBlank())
				{
					pxActorObject = GetActor(pxEventSound->m_xTargetActorName.GetString());
				}
				
				//initialise properties for sound
				InputWavFileProp xProp;
				if(pxActorObject)
				{
					xProp.m_pxLoc = &pxActorObject->m_xSoundPosition;
					xProp.m_fInnerRange = 5.0f;
					xProp.m_fOuterRange = 40.0f;
				}
				xWriter.AddInputWavFile(pxEventSound->m_xSoundName.GetString(),Cutscene->Timer,&xProp);

			}
		}

		for(LIF<Tools_CutsceneActor_Object*> xActorLIF(&ActorObjects);!xActorLIF.done();xActorLIF.next())
		{
			xActorLIF()->UpdateSoundPosition();
		}

		//update the wave file with this frame's output
		xWriter.UpdateOutput(Cutscene->Timer,&pxCamera->Position,&pxCamera->Orientation);

		fPreviousTime = Cutscene->Timer;
		//advance the cutscene
		Cutscene->Update(0.04f);
		Cutscene->Render();
        	
	}while(fPreviousTime<Cutscene->Timer);

	xWriter.FinishOutput();

	Cutscene->Finish();
	Cutscene->m_bRecordingWavFile = false;

	//set the filename for the soundtrack for the cutscene
	Cutscene->UseSoundTrack = true;
	Cutscene->SoundTrackName = szFilename;

	Tools_Timed_Message_System::AddMessage(0xffffff,"%s written",szFilename);
}


void Tools_Cutscene_Object::RenderSubtitle(float& x,float& y)
{
	if(!Cutscene->IsPlaying()) return;

	
	if(!Cutscene->m_uSubtitleHash) return;
	if(Cutscene->m_fSubtitleEndTime && Cutscene->Timer > Cutscene->m_fSubtitleEndTime) return;

	
	//show the text from the string file if we have it
	const Asura_UnicodeChar* wszText = Asura_HashedLocalisedText::GetString(uASURA_HASHEDTEXTPAGE_CUTSCENE, Cutscene->m_uSubtitleHash);
	if(wszText)
	{
		Asura_Text_Output::Print(x,y++,0xffffffff,0,"Subtitle: %S",wszText);
	}
	else
	{
		//show the textID

		Asura_Text_Output::Print(x,y++,0xffffffff,0,"Subtitle ID : %s",Tools_Cutscene_Subtitles::GetSubtitleID(Cutscene->m_uSubtitleHash));
	}
}

void Tools_Cutscene_Object::PickWavFile()
{
	Tools_FileSystem_DialogInfo xInfo;

	xInfo.DefaultExtension = "";
	char szDefaultName[MAX_PATH] = "";
	if(!Cutscene->SoundTrackName.IsBlank())
	{
		Cutscene->LocaliseSoundName(Cutscene->SoundTrackName.GetString(),szDefaultName,MAX_PATH);
	}
	
	xInfo.DefaultFilename = szDefaultName;

	char szDefaultPath[MAX_PATH];
	Cutscene->LocaliseSoundName("Sounds\\Cutscene\\",szDefaultPath,MAX_PATH);


	xInfo.DefaultPath = szDefaultPath;
	xInfo.FileFilter = "Sound Files\0*.wav;*.bik;\0All Files\0*.*;\0";
	xInfo.WindowTitle = "Pick soundtrack file...";
	xInfo.AllowMultipleSelect = false;

	#if defined ASURA_USE_AUDIO_SYSTEM

		Asura_String xName;
		const Asura_Hash_ID uSelectedStream = Tools_Audio_StreamingOverlay::SelectStream();

		if( uSelectedStream != ASURA_HASH_ID_UNSET )
		{
			const Asura_Audio_StreamInfo* const pxStream =
				Asura_Audio_System::GetStreamInfo( uSelectedStream );

			if( !pxStream ) return;
			
			xName = pxStream->m_szFilename;
		}

	#else
		Asura_String xName = Tools_FileSystem::GetLoadFilename(xInfo);
	#endif

	if(!xName.IsNull())
	{
		//make sure its in the sounds directory
		_strlwr(xName.GetString());
		char *pszStart = strstr(xName.GetString(),"sounds\\");
		if(!pszStart)
		{
			Message_Overlay xOverlay("Soundtrack file should be in a subdirectory of Sounds");
			xOverlay.DoModal();
		}
		else
		{
			char szSoundName[MAX_PATH];
			Cutscene->DelocaliseSoundName(pszStart,szSoundName,MAX_PATH);

			Cutscene->SoundTrackName = szSoundName;
			Cutscene->UseSoundTrack = true;
			Tools_SaveAsura::SetCutscenesModified();
		}
	}

	UpdateOverlayLabels();
	UpdateLengthFromSoundTrack();
}


void Tools_Cutscene_Object::ExportCutscene()
{
	//ask for a filename
	Tools_FileSystem_DialogInfo xInfo;

	xInfo.DefaultExtension = "ExportedCutscene";

	xInfo.DefaultFilename = Cutscene->CutsceneName.GetString();

	xInfo.DefaultPath = "";
	xInfo.FileFilter = "Exported Cutscenes\0*.ExportedCutscene\0";
	xInfo.WindowTitle = "Export cutscene as ...";

	Asura_Char* pszName = Tools_FileSystem::GetSaveFilename(xInfo);
	
	if (!pszName)
	{
		return;
	}

	//name the exported cutscene based on the filename
	Asura_String xFullFilename = pszName;
	Asura_String xExportedCutsceneName;
	xFullFilename.SplitFilename(0,&xExportedCutsceneName,0);


	//make a copy of the cutscene
	Asura_Cutscene* pxCutscene = Asura_Cutscene::AllocateCutscene( Cutscene->GetCutsceneType(),xExportedCutsceneName.GetString());
	pxCutscene->Copy(Cutscene);

	//need to reset any guids to avoid problems loading elsewhere
	pxCutscene->ResetGuidsAndMessages();

	//save the file
	Asura_SaveBlock xSaveBlock;
	
	//save textures used
	Asura_NameTree xTextureTree;
	pxCutscene->BuildUsedTextureTree(&xTextureTree);

	if(xTextureTree.GetNumberOfNodes())
	{
		Asura_Chunk_TextureNames::Create(&xTextureTree,&xSaveBlock);
	}


	
	Asura_Chunk_Cutscene::Create(pxCutscene,xSaveBlock);
	xSaveBlock.AddAsuraTerminator();
	if(xSaveBlock.SaveFile(pszName,true))
	{
		Tools_Timed_Message_System::AddMessage(0x00ff00,"Saved %s.",pszName);
	}
	else
	{
		Tools_Timed_Message_System::AddMessage(0xff0000,"Failed to save %s.",pszName);
	}

	//kill of the copy of the cutscene
	pxCutscene->PreDestructionCleanup();
	delete pxCutscene;
}

void Tools_Cutscene_Object::SortActors()
{
	//get a list of actors to sort
	List<Asura_Cutscene_Actor*> xUnplacedActors;
	for(int i=0;i<Cutscene->NumberOfActors;i++)
	{
		xUnplacedActors.add_entry(Cutscene->Actors[i]);
	}

	int iIndex = 0;
	while(xUnplacedActors.size())
	{
		//search for an actor that can be placed
		LIF<Asura_Cutscene_Actor*> xActorLIF1(&xUnplacedActors);
		LIF<Asura_Cutscene_Actor*> xActorLIF2(&xUnplacedActors);
		for(;!xActorLIF1.done();xActorLIF1.next())
		{
			for(xActorLIF2.restart();!xActorLIF2.done();xActorLIF2.next())
			{
				if(xActorLIF2()->NeedsUpdateBefore(xActorLIF1()))
				{
					//can't place actor 1 yet
					break;
				}
			}
			if(xActorLIF2.done())
			{
				//no remaining actors need to be before actor 1
				break;
			}
		}
		if(xActorLIF1.done())
		{
			//impossible to get an ordering that will satisfy all the conditions...
			Cutscene->Actors[iIndex++] = xUnplacedActors.first_entry();
			xUnplacedActors.delete_first_entry();
		}
		else
		{
			//safe to place actor 1 since no unplaced ones have to go before it
			Cutscene->Actors[iIndex++] = xActorLIF1();
			xActorLIF1.delete_current();
		}
	}
	Asura_Assert(iIndex==Cutscene->NumberOfActors,"Error sorting actors.");

}

bool Tools_Cutscene_Object::IsServerCutscene() const
{
	return Cutscene->PlaysOnServer;
}


Tools_Cutscene_Object* Tools_Cutscene_Object::FindCutsceneObject(const char* pszName)
{
	if(!pszName) return 0;

	
	for(LIF<Tools_Object*> xObjectLIF(&Tools_Object_System::ObjectList);!xObjectLIF.done();xObjectLIF.next())
	{
		if(xObjectLIF()->Type != TOT_Cutscene) continue;

		Tools_Cutscene_Object* pxObject = (Tools_Cutscene_Object*) xObjectLIF();
		if(!strcmp(pxObject->GetName(),pszName))
		{
			return pxObject;
		}
	}
	return 0;
}

void Tools_Cutscene_Object::UpdateLengthFromSoundTrack()
{
	if(!Cutscene->UseSoundTrack) return;
	if(!(Cutscene->m_iCutsceneFlags & AsuraCutsceneFlag_TakeLengthFromSoundTrack)) return;
	if(Cutscene->SoundTrackName.IsBlank()) return;

	//get the localised name for the soundtrack
	char szLocalFilename[MAX_PATH];
	Cutscene->LocaliseSoundName(Cutscene->SoundTrackName.GetString(),szLocalFilename,MAX_PATH);

	//get the length from the sound system
	#ifdef ASURA_USE_AUDIO_SYSTEM
	// actually dont
	// AUDIOTODO: get the length of the passed wav
	#else
	Asura_Handle iSoundHandle = Asura_Sound_System::CreateStreamingSound( szLocalFilename );
	if(iSoundHandle == 	ASURA_INVALID_HANDLE) return;

	float fSoundTrackLength = Asura_Sound_System::GetStreamingSoundLength(iSoundHandle);

	if(fSoundTrackLength > 0.0f)
	{
		Cutscene->SetCutsceneEndTimeFromRealTimeDuration(fSoundTrackLength);
	}
	
	Asura_Sound_System::ReleaseStreamingSound(iSoundHandle);
	#endif
}


Tools_Cutscene_Track* Tools_Cutscene_Object::AllocateToolsTrack( Asura_Cutscene_Track* pxTrack, Tools_CutsceneActor_Object* pxActorToolsObj, int iTrackID )
{
    return new Tools_Cutscene_Track( pxTrack, pxActorToolsObj, iTrackID );
}


void Tools_Cutscene_Object::ConvertInitialAnimations(Asura_Cutscene* pxCutscene)
{
	List<Asura_Cutscene_Event*>	 xNewEventList;

	//check each actor to see if it has an initial animation set
	for(int i=0; i<pxCutscene->NumberOfActors; i++)
	{
		if(!pxCutscene->Actors[i]->IsHierarchyActor()) continue;

		Asura_Cutscene_Actor_Hierarchy* pxActor = (Asura_Cutscene_Actor_Hierarchy*) pxCutscene->Actors[i];
		if(pxActor->AnimID < 0) continue;

		//Need to create an animation change event for this actor , and copy the settings across
		Asura_Cutscene_Event_AnimationChange* pxEvent = (Asura_Cutscene_Event_AnimationChange*) Asura_Cutscene_Event::AllocateEvent(ACET_AnimationChange,pxCutscene);
		Asura_Assert(pxEvent,"");
		xNewEventList.add_entry(pxEvent);

		pxEvent->m_pxTargetActor = pxActor;
		pxEvent->m_xTargetActorName = pxActor->ActorName;

		//copy the settings , clearing them in the actor as we go
		pxEvent->m_iAnimationIndex = pxActor->AnimID;
		pxActor->AnimID = -1;
		pxEvent->m_iAnimationFlags = pxActor->m_iAnimationFlags;
		pxActor->m_iAnimationFlags = 0;
		pxEvent->m_fAnimDelay = pxActor->m_fAnimationDelay;
		pxActor->m_fAnimationDelay = 0;

	}
	if(!xNewEventList.size()) return;

	//Add the new events to the start of the event array
	int iNumNewEvents = xNewEventList.size();

	//enlarge the array of events
	pxCutscene->NumberOfEvents += iNumNewEvents;
	Asura_Cutscene_Event** ppxNewEvents;
	Asura_ResourceAllocateArray(ppxNewEvents, pxCutscene->NumberOfEvents);
	
	//copy new events to the start
	for(int i=0; i<iNumNewEvents; i++)
	{
		ppxNewEvents[i] = xNewEventList[i];
	}
	//and old events after that
	for(int i=iNumNewEvents;i<pxCutscene->NumberOfEvents;i++)
	{
		ppxNewEvents[i] = pxCutscene->Events[i-iNumNewEvents];
	}

	//replace the old array
	Asura_ResourceDeallocateArray(pxCutscene->Events);
	pxCutscene->Events = ppxNewEvents;

	//update any old events that refer to another event by index
	for(int i=iNumNewEvents; i<pxCutscene->NumberOfEvents; i++)
	{
		Asura_Cutscene_Event* pxEvent = pxCutscene->Events[i];
		
		if(pxEvent->m_iParentEventIndex>=0)
		{
			pxEvent->m_iParentEventIndex +=  iNumNewEvents;
		}

		if(pxEvent->m_iType == ACET_LoopBack)
		{
			Asura_Cutscene_Event_LoopBack* pxLoopEvent = (Asura_Cutscene_Event_LoopBack*) pxEvent;
			if(pxLoopEvent->m_iEventToLoopBackTo >= 0)
			{
				pxLoopEvent->m_iEventToLoopBackTo += iNumNewEvents;
			}
		}
	}	
}

static int ReorderActorForTimelineCallback(const void* pData1, const void* pData2)
{
	const Asura_Cutscene_Actor** ppxActor1 = (const Asura_Cutscene_Actor**) pData1;
	const Asura_Cutscene_Actor** ppxActor2 = (const Asura_Cutscene_Actor**) pData2;

	if((*ppxActor1)->m_uToolsSortOrder < (*ppxActor2)->m_uToolsSortOrder) return -1;
	if((*ppxActor1)->m_uToolsSortOrder > (*ppxActor2)->m_uToolsSortOrder) return 1;
	return 0;
}

void Tools_Cutscene_Object::ReorderActorsForTimeline()
{
	Asura_Cutscene* pxCutscene = Tools_Mode_Cutscene::GetCurrentCutscene();
	if(!pxCutscene) return;

	int iNumActors = pxCutscene->NumberOfActors;
	if(!iNumActors) return;

	
	//get a list of actors using the current timeline ordering
	Asura_Cutscene_Actor** ppxActorList = new Asura_Cutscene_Actor*[iNumActors];
	u_int uHighestSortOrder = 0;
	for(int i=0; i<iNumActors; i++)
	{
		ppxActorList[i] = pxCutscene->Actors[i];
		if(ppxActorList[i]->m_uToolsSortOrder != 0xffffffff)
		{
			uHighestSortOrder = Asura_Maths::Max(uHighestSortOrder,ppxActorList[i]->m_uToolsSortOrder);
		}
	}
	//Give a sort value for any actors that haven't had one set yet , so that
	//they will have unique values at the end.
	for(int i=0; i<iNumActors; i++)
	{
		if(ppxActorList[i]->m_uToolsSortOrder == 0xffffffff)
		{
			ppxActorList[i]->m_uToolsSortOrder = ++uHighestSortOrder;
		}
	}

	Asura_SortAndSearch::Quicksort(ppxActorList, iNumActors,ReorderActorForTimelineCallback);

	//get the actor names for the sorting overlay
	const char** pszNameList = new const char*[pxCutscene->NumberOfActors];
	for(int i=0; i<iNumActors; i++)
	{
		pszNameList[i] = ppxActorList[i]->ActorName.GetString();
	}

	//show the overlay
	ListOrdering_Overlay xOverlay("Sort actors for timeline", iNumActors, pszNameList, (void**)ppxActorList);
	if(xOverlay.DoModal())
	{
		//update the sort value in all the actors
		for(int i=0; i<iNumActors; i++)
		{
			ppxActorList[i]->m_uToolsSortOrder = i;
		}
		Tools_Cutscene_Timeline::Invalidate();

		Tools_SaveAsura::SetCutscenesModified();

	}

	delete [] ppxActorList;
	delete [] pszNameList;
	
}

void Tools_Cutscene_Object::UpdateBackupVariables()
{
	m_xPositionBackup = Position;
	m_xOrientationBackup = Orientation;
}

bool Tools_Cutscene_Object::IsEnabled_LoopToStartTime()
{
	return Cutscene && (Cutscene->m_iCutsceneFlags & AsuraCutsceneFlag_LoopCutscene);
}

Asura_Entity_Server* Tools_Cutscene_Object::GetServerEntity()
{
	return m_pxCutsceneController;
}

//////////////////////////////////////////////////////////////////////////////////////////////////
/*---------------**
** Cutscene Undo **
**---------------*/


/////////Cutscene_Changed
void CutsceneModeUndo::Log_Cutscene_Changed(Tools_Cutscene_Object* pxCutsceneObject)
{
	RootUndoData** ppxUndo = AllocateNextUndoData();
	if(!ppxUndo) return;
	
	*ppxUndo = new CutsceneUndoData_Cutscene_Changed(pxCutsceneObject);
}

CutsceneUndoData_Cutscene_Changed::CutsceneUndoData_Cutscene_Changed(Tools_Cutscene_Object* pxCutsceneObject)
{

	m_xCutsceneName = pxCutsceneObject->GetName();

	Position = pxCutsceneObject->m_xPositionBackup;
	Orientation = pxCutsceneObject->m_xOrientationBackup;

	//fill in desc of change
	SetDescription("Cutscene Moved  '%s'",pxCutsceneObject->GetName());
	
	//changes have been noted , so update the backup variables
	pxCutsceneObject->UpdateBackupVariables();
}

RootUndoData* CutsceneUndoData_Cutscene_Changed::ProcessUndoData()
{
	Tools_Cutscene_Object* pxCutsceneObject = GetCutsceneObject(m_xCutsceneName.GetString());
	Asura_Assert(pxCutsceneObject,"");

	//update backup variables , so they can be used for the reverse undo data
	pxCutsceneObject->UpdateBackupVariables();

	//fill in the details from the undo data
	pxCutsceneObject->Position = Position;
	pxCutsceneObject->Cutscene->Position = Position;
	pxCutsceneObject->Orientation = Orientation;

	
	return new CutsceneUndoData_Cutscene_Changed(pxCutsceneObject);
}

int CutsceneUndoData_Cutscene_Changed::GetPriority() const
{
	return CutsceneModeUndo::PRIORITY_CUTSCENE_CHANGED;
}



