#include "Core/Asura.h"
#include "Cutscene/Tools_Mode_Cutscene.h"

#include "Animation/Asura_Animation.h"
#include "Camera/Asura_Camera.h"
#include "ChunkSystem/Asura_Chunk_HashedLocalisedText.h"
#include "ChunkSystem/Asura_Chunk_Loaders.h"
#include "ChunkSystem/Asura_Chunk_FMVSubtitles.h"
#include "ChunkSystem/Asura_SaveBlock.h"
#include "Core/Asura_HashedLocalisedText.h"
#include "Core/Asura_Timers.h"
#include "Core/Asura_TextureManagement.h"
#include "Core/Asura_UserInput.h"
#include "Core/Asura_LocalisedText.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_Track.h"
#include "Cutscene/Asura_FMVSubtitles.h"
#include "Debugging/Asura_Assert.h"
#include "Entity/Cutscene/Asura_ServerEntity_CutsceneController.h"  // update when cutscene renamed
#include "Entity/ParticleGenerator/Asura_ClientEntity_PFX_Effect.h"
#include "Entity/Triggers/Asura_ClientEntity_SoundController.h"
#include "Entity/Triggers/Asura_ServerEntity_SoundController.h"
#include "Entity/Asura_Entity_Classification.h"
#include "Entity/Asura_StaticMessage_System.h"
#include "Interface/Asura_Menubar_System.h"
#include "Interface/Asura_Menubar.h"
#include "Interface/Asura_Overlay_System.h"
#include "Interface/Asura_Overlay.h"
#include "Interface/Asura_Dialog.h"
#include "Interface/Asura_Interface.h"
#include "Rendering/Asura_Text_Output.h"
#include "Rendering/Asura_Viewport.h"
#include "Rendering/Asura_Render.h"
#ifdef ASURA_USE_DIALOGUE_SYSTEM
#include "Sound/Asura_Dialogue.h"
#include "Sound/Asura_Dialogue_System.h"
#endif
#include "SpecialFX/Asura_FullScreenSFX_System.h"
#include "SpecialFX/PFX/Asura_PFX_System.h"
											
#include "Tools_Overlays.h"
#include "Tools_System.h"
#ifdef ASURA_USE_ATTRACTORS
#include "Attractors/Tools_Object_Attractor_Node.h"
#endif
#include "Cutscene/Tools_Cutscene_Object.h"
#include "Cutscene/Tools_CutsceneActor_Object.h"
#include "Cutscene/Tools_Cutscene_Track.h"
#include "Cutscene/Tools_Cutscene_Event.h"
#include "Cutscene/Tools_Cutscene_Timeline.h"
#include "Cutscene/Tools_Cutscene_Dialog.h"
#include "Cutscene/Tools_Cutscene_Subtitles.h"
#include "Cutscene/Tools_CutsceneModeAVI.h"
#include "Cutscene/Tools_Cutscene_RecordAnimation.h"
#include "Entity/Tools_Entity_Object.h"
#include "Entity/Tools_Entity_CutsceneController_Object.h"
#include "Hierarchy/Tools_Animation_Files.h"
#include "Load&Save/Tools_SaveAsura.h"
#include "Load&Save/Tools_ModeDataFile.h"
#include "Misc/Tools_AVI_Writer.h"
#include "Misc/Tools_Dialog.h"
#include "Misc/Tools_Help.h"
#include "Misc/Tools_Inifile.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"
#include "Shapes/Tools_Shapes.h"
#include "Sound/Tools_Mode_Sound.h"
#include "Sound/Tools_StreamingBackgroundSound_Object.h"
#include "Sound/Asura_Audio_Voice.h"

#define BUTTONS_TEXTURE_NAME "\\tools\\buttons.bmp"

static int ButtonsTextureIndex = -1;

CutsceneModeUndo* CutsceneUndo = NULL;

static int g_iPreviousScreenWidth = 0;//used for detecting changes in screen size
static int g_iPreviousScreenHeight = 0;//used for detecting changes in screen size
static const char* szASURA_HASHEDTEXTPAGE_CUTSCENE = "cutscene";

///////////////////////////////////////////////////////////////////////////////////////
/*--------------**
** Help Strings **
**--------------*/
static const char* CutsceneModeHelpStrings [] =
{
	"T :           Enter timeline screen",
	"P :           Add point after selected track point",
	"Shift+P :     Add point before selected track point",
	"I :           Align point with track",
	"",
	"Cutscene playback keys :",
	"Space :       Pause cutscene/reset to start if already paused",
	"Enter :       Start playback/unpause",
	"Ctrl+Left :   Step back a 'frame' at a time",
	"Ctrl+Right :  Step forwards a 'frame' at a time",
	"Shift+Left :  Rewind (while held down)",
	"Shift+Right : Fast Forward (while held down)",
	"C :           Disable/enable cutscene camera. (Not while flying an object)",
	"H :           Hide/show hierarchical characters",
	"O :           Hide/show tools objects in cutscene playback.",
	"Ctrl+Z :      Undo",
	"Ctrl+Y :      Redo",
	"< :           Undo",
	"> :           Redo",
	"",
	"Timeline screen keys :",
	"LMB :         Select (replacing existing selection)",
	"Ctrl + LMB :  Add to selection",
	"Shift+LMB     Deselect",
	"Hold mouse button for rectangular selection/deselection",
	"Left click and drag on item to adjust time of selected items",
//	"Hold control while dragging to snap to the start/ends of other events",
	"L :           Clear selection",
	"Left :        Scroll left",
	"Right :       Scroll right",
	"Up :          Scroll up",
	"Down :        Scroll down",
	"Hold Shift    Scroll faster",
	"Drag MMB      Scroll left/right/up/down",
	"Z :           Zoom in",
	"Shift+Z :     Zoom out",
	"Mousewheel :  Zoom in/out",
	"T :           Adjust time of selected items",
//	"Shift+T :     Adjust time , snapping to the item that the cursor is over",
	"S :           Enable/disable snapping of items being dragged",
	"R :           Run cutscene to time based on item under mouse (or horizontal position)",
	"A :           Edit hidden actors",
	"E :           Edit hidden events",
	NULL
};

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

class Tools_MessageLogAction_Cutscene : public Tools_MessageLogAction
{
public :
	Tools_MessageLogAction_Cutscene(const Asura_Cutscene* pxCutscene) {m_xName = pxCutscene->GetName();}

	virtual void Execute()
	{
		Tools_Cutscene_Object* pxCutsceneObject = Tools_Cutscene_Object::FindCutsceneObject(m_xName.GetString());
		if(pxCutsceneObject)
		{
			Tools_Object_System::GotoAndSelect(pxCutsceneObject);
		}
	}

	Asura_String m_xName;
};

//////////////////////////////////////////////////////////////////////////////////////////
/*---------------------**
** Tools_Mode_Cutscene **
**---------------------*/

//static member variables
Tools_Mode_Cutscene* Tools_Mode_Cutscene::s_pxThis = 0;
Tools_Cutscene_Object* Tools_Mode_Cutscene::CurrentCutscene = 0;
CutsceneTimerMode Tools_Mode_Cutscene::s_eTimerMode = CUTSCENETIMER_SECONDS;


void Tools_Mode_Cutscene::InitialiseMode()
{
	//initialise everything
	CutsceneModeOverlayPosition = Asura_Vector_2(3.0f,28.0f);
	CutsceneModeOverlayOpen = true;

	CutsceneEditOverlayPosition = Asura_Vector_2(3.0f,10.0f);
	CutsceneEditOverlayOpen = true;

	CutscenePlaybackOverlayPosition = Asura_Vector_2(3.0f,20.0f);
	CutscenePlaybackOverlayOpen = true;

	CurrentCutscene = NULL;

	HideCutsceneObjects = false;
	m_bHideTrackObjects = false;
	m_bHideActorObjects = false;
	HideEventObjects = false;
	HideStaticObjects = false;
	m_bHideContainerObjects = false;
	m_bUseRecordedWavFile = false;
	m_bHighlightHierarchyActors = false;
	m_bHideOtherEntities = true;
	m_bDisablePFX = false;

	CutscenePlayback_Active = false;
	CutscenePlayback_Refresh = false;
	CutscenePlayback_HideObjects = true;
	CutscenePlayback_DeltaTime = 0;
	CutscenePlayback_TimesliderModified = false;
	CutscenePlayback_Timer = 0;
	CutscenePlayback_Speed = 1.0f;

	m_fScreenshotsFrameRate = 60.0f;
	m_bBeginScreenshotRecording = false;
	m_bRecordingScreenshots = false;

	
	
	//create the menubar
	MenuHandle = Asura_Menubar_System::CreateMenuSection();
	Asura_Menubar_Title* title;

	title = Asura_Menubar_System::AddTitle(MenuHandle,"Overlays");
	title->AddItem("Cutscene Mode",_ToggleCutsceneModeOverlay,_CutsceneModeOverlayIsOn);
	title->AddItem("Cutscene Edit",_ToggleCutsceneEditOverlay,_CutsceneEditOverlayIsOn);
	title->AddItem("Playback",_ToggleCutscenePlaybackOverlay,_CutscenePlaybackOverlayIsOn);
	title->AddItem("Actor",Tools_CutsceneActor_Object::ToggleOverlay,Tools_CutsceneActor_Object::OverlayIsOn);
	title->AddItem("Track Point",Tools_CutsceneTrackPoint_Object::ToggleOverlay,Tools_CutsceneTrackPoint_Object::OverlayIsOn);

	//load values from .dat file
	TransferModeData(TOOLSMODEDATAFILE_LOAD);
	
	//create overlays
	CreateCutsceneModeOverlay();

	Tools_Cutscene_Subtitles::Initialise();


	//create cutscene objects for all existing cutscenes
	Tools_Cutscene_Object::CreateAllCutsceneObjects();

	//create tools objects for all the entities (for message targeting)
	Tools_Entity_Object::CreateAllObjects();
    Tools_Mode_Sound::SetShowRadius(false);
	#ifdef ASURA_USE_ATTRACTORS
	Tools_Object_Attractor_Node::CreateAttractorObjects();
	#endif

	Tools_Object_System::EnableAllControls(true);

	CurrentCutscene = NULL;

	Tools_Help::AddHelpTopic("Cutscene",CutsceneModeHelpStrings);


	ButtonsTextureIndex = Asura_Texture_Management::AddTexture(BUTTONS_TEXTURE_NAME);
	Asura_Texture_Management::LoadTextures();

	Asura_Cutscene::ToolsPlayback = true;


	CutsceneUndo = AllocateCutsceneModeUndo();
	CutsceneUndo->Initialise();
	CutsceneUndo->SetVerbose(true);
	CutsceneUndo->SetKeepUndoBatchOpenForShortTimeAfterUndoLogged(true);

	//disable object system undo , since it will clash with the cutscene specific undo
	Tools_Object_System::DisableUndo();

    // (MH - 27.09.2002) hack to get in hard-coded anims for GF2
    FillInAnyMissingData();

	Tools_CutsceneModeAVI::Initialise();

	Tools_Render_Options::SetPFXControlledByMode(true);

	Verify(true);

	//filter any messages sent while editing cutscenes
	Asura_StaticMessage_System::SetSendMessageCallback(SendStaticMessage);
}


void Tools_Mode_Cutscene::ShutdownMode(void)
{
	Tools_Render_Options::SetPFXControlledByMode(false);

	Tools_CutsceneModeAVI::Shutdown();
	Tools_Cutscene_RecordAnimation::AbortRecording();

	delete CutsceneUndo;
	CutsceneUndo = NULL;

	Tools_Object_System::EnableUndo();


	Tools_Help::RemoveHelpTopic("Cutscene");

	EditCutscene(0);
	
	Asura_Cutscene::ToolsPlayback = false;

	//delete all the objects
    Tools_Mode_Sound::DestroyPhononToolsObjects();
	Tools_Object_System::DeleteAllModeSpecific();

	//unhide the various object types that were hidden for this mode
	Tools_Object_System::SetTypeHidden(TOT_Cutscene,ObjectHideFlag_User,false);
	Tools_Object_System::SetTypeHidden(TOT_CutsceneActor,ObjectHideFlag_User,false);
	Tools_Object_System::SetTypeHidden(TOT_CutsceneTrackPoint,ObjectHideFlag_User,false);
	Tools_Object_System::SetTypeHidden(TOT_CutsceneEvent,ObjectHideFlag_User,false);
    Tools_Object_System::SetTypeHidden(TOT_Entity_PhysicsObject,ObjectHideFlag_User,false);

	//remove the individual hide flags from any objects that persist between modes.
	for(LIF<Tools_Object*> xObjectLIF(&Tools_Object_System::ObjectList);!xObjectLIF.done();xObjectLIF.next())
	{
		xObjectLIF()->SetHiddenFlag(ObjectHideFlag_Cutscene|ObjectHideFlag_Mode,false);
	}


	//re-enable PFX
	Asura_PFX_System::DisablePFXSystem(false);


	//save values to .dat file
	TransferModeData(TOOLSMODEDATAFILE_SAVE);
	
	//shutdown the menu
	if(MenuHandle != ASURA_INVALID_HANDLE)
	{
		Asura_Menubar_System::DestroyMenuSection(MenuHandle);
		MenuHandle = ASURA_INVALID_HANDLE;
	}

	//close overlays
	Asura_Overlay_System::CloseOverlays(TOOLS_OVERLAY_MODE);

	Asura_Overlay_System::CloseOverlays(TOOLS_OVERLAY_CUTSCENE);
	Asura_Overlay_System::CloseOverlays(TOOLS_OVERLAY_CUTSCENE_PLAYBACK);
	Asura_Overlay_System::CloseOverlays(TOOLS_OVERLAY_CUTSCENE_PLAYBACKSLIDER);

	
	Tools_Object_System::EnableAllControls(false);

	Asura_Cutscene::DisableSoundTrack(false);

	Asura_ClientCutscene_Actor_Camera::Disable = false;
	Asura_ClientCutscene_Actor_Hierarchy::DontRender = false;

	Tools_Cutscene_Timeline::Invalidate();

	Tools_Cutscene_Subtitles::Shutdown();

	Tools_Cutscene_Event::DeleteEventListStrings();

	Asura_StaticMessage_System::SetSendMessageCallback(0);
}

CutsceneModeUndo* Tools_Mode_Cutscene::AllocateCutsceneModeUndo()
{
	return new CutsceneModeUndo;
}

void Tools_Mode_Cutscene::TransferModeData(int mode)
{
	//open the file
	Tools_ModeDataFile file("CutsceneMode.dat",mode);

	//tranfer the data
	//(none of these should be removed , and new data should go on the end, otherwise
	//the files will get screwed up)
	file.Transfer(CutsceneModeOverlayPosition);
	file.Transfer(CutsceneModeOverlayOpen);
	file.Transfer(CutsceneEditOverlayPosition);
	file.Transfer(Tools_CutsceneActor_Object::OverlayPosition);
	file.Transfer(Tools_CutsceneTrackPoint_Object::OverlayPosition);
	file.Transfer(CutscenePlaybackOverlayPosition);
	file.Transfer(Tools_Cutscene_Event::OverlayPosition);
	file.Transfer(CutsceneEditOverlayOpen);
	file.Transfer(Tools_CutsceneActor_Object::OverlayOpen);
	file.Transfer(Tools_CutsceneTrackPoint_Object::OverlayOpen);
	file.Transfer(CutscenePlaybackOverlayOpen);
	file.Transfer(m_bUseRecordedWavFile);
	file.Transfer(Tools_CutsceneModeAVI::s_xOverlayPosition);
}

void Tools_Mode_Cutscene::Update(void)
{
	//Flag the undo to start a new undo batch
	//(unless snapping to vertex , in which case we don't , so
	//that the operation will get undone in one go).
	if(Tools_Object_System::IsDoingSnapToVertex())
	{
		CutsceneUndo->KeepUndoBatchOpenThisFrame();
	}
	
	CutsceneUndo->MarkUndoStartIfEndOfBatch();
	
	UpdateTimelineBar();
	
	//act upon keyboard input
	HandleKeyboard();
	
	if(CurrentCutscene)
	{
		CurrentCutscene->UpdateCutsceneEdit();
		CurrentCutscene->UpdateTracks();
	}

	Tools_CutsceneModeAVI::Update();

	if(Tools_Cutscene_RecordAnimation::IsRecording())
	{
		Tools_Cutscene_RecordAnimation::AddFrame();
	}

	if(!TheAviWriter.IsWritingFile())
	{
		if (m_bBeginScreenshotRecording)
		{
			BeginRecordScreenshots();
		}
		else if (m_bRecordingScreenshots && !Tools_System::IsRecordingScreenshots())
		{
			EndRecordScreenshots();
		}
	}

	if(CurrentCutscene && (CurrentCutscene->Cutscene->IsPlaying() || CutscenePlayback_Refresh || CutscenePlayback_TimesliderModified))
	{
		if(CutscenePlayback_Refresh || !CutscenePlayback_Active)
		{
			//A change has been made to the asura cutscene data.
			//if the cutscene is playing , then it needs to be restartd and advanced to the right time
			float time = CurrentCutscene->Cutscene->Timer;
			if(!CutscenePlayback_Active)
			{
				StartPlayback();
				CutscenePlayback_Speed = 0.0f;
			}

			if(CurrentCutscene->Cutscene->IsPlaying())
			{
				CurrentCutscene->Cutscene->Finish();
				Asura_Entity_System::PruneEntityLists();
			}

			OnRestart();

			Asura_Cutscene::DisableSoundTrack(!m_bUseRecordedWavFile);
			CurrentCutscene->Cutscene->Initialise();
			CurrentCutscene->Cutscene->AdvanceToTime(time);
			CutscenePlayback_Refresh = false;
		}
		else
		{
			if(CurrentCutscene->Cutscene->Timer == CurrentCutscene->Cutscene->CutsceneEndTime)
			{
				if(TheAviWriter.IsWritingFile())
				{
					Tools_System::EndAVI();
				}
				if (m_bRecordingScreenshots)
				{
					EndRecordScreenshots();
				}
			}
			bool bPlaySound = false;


			if(CutscenePlayback_TimesliderModified)
			{
				CutscenePlayback_DeltaTime = CutscenePlayback_Timer - CurrentCutscene->Cutscene->Timer;
			}
			//If no frame time for the cutscene has been supplied through key controls , get a time
			//from the game frame time (forwards./backwards as applicable)
			else if(!CutscenePlayback_DeltaTime && CutscenePlayback_Speed)
			{
				CutscenePlayback_DeltaTime += Asura_Timers::GetGameTimeSlice()*CutscenePlayback_Speed;

				if(CutscenePlayback_Speed==1.0f) bPlaySound = true;
			}
		
			if(TheAviWriter.IsWritingFile())
			{
				//while recording an avi , use the avi frame rate to determine frame time
				CutscenePlayback_DeltaTime = 1.0f/TheAviWriter.GetAviFrameRate();
				//apply any time scale
				CutscenePlayback_DeltaTime *= Asura_Timers::GetGameTimeScale();
				bPlaySound = false;
			}
			if (m_bRecordingScreenshots)
			{
				CutscenePlayback_DeltaTime = 1.0f/m_fScreenshotsFrameRate;
				//apply any time scale
				CutscenePlayback_DeltaTime *= Asura_Timers::GetGameTimeScale();
				bPlaySound = false;
			}
			if(Tools_Cutscene_RecordAnimation::IsRecording())
			{
				float fTargetTime = Tools_Cutscene_RecordAnimation::GetDesiredCutsceneTimer();
				CutscenePlayback_DeltaTime = fTargetTime - CurrentCutscene->Cutscene->Timer;
				bPlaySound = false;
			}
		
			//apply the frame time to the cutscene
			if(CutscenePlayback_DeltaTime<0)
			{
				//need to restart some stuff since cutscene will be played again from beginning
				OnRestart();
				
				float time = CurrentCutscene->Cutscene->Timer + CutscenePlayback_DeltaTime;
				if(time<0) time = 0;
				CurrentCutscene->Cutscene->AdvanceToTime(time);
				UpdateSoundTrack(0.0f);
			}
			else
			{
				CurrentCutscene->Cutscene->Update(CutscenePlayback_DeltaTime);
				UpdateSoundTrack(0.2f);
			}
		}
		CutscenePlayback_DeltaTime = 0;
		CutscenePlayback_Timer = CurrentCutscene->Cutscene->Timer;
		CutscenePlayback_TimesliderModified = false;

		CurrentCutscene->PerframeUpdateWhilePlaying();
		
		
		#if 1
		//make sure the camera object knows that the camera has moved
		Tools_Movement::GetCameraObject()->UpdatePositionFromAsuraData();
		#endif

		//update static message system to deal with delayed messages
		Asura_StaticMessage_System::Update();

	}

	UpdateObjectVisibilities();


	Tools_Cutscene_Timeline::SetCutscene(CurrentCutscene);
	if(CurrentCutscene)
	{
		//see if an actor or track point is selected
		Tools_CutsceneActor_Object* actorObject = NULL;
		Tools_Object* selectedObject = Tools_Object_System::SelectedObject;
		if(selectedObject)
		{
			switch(selectedObject->Type)
			{
				case TOT_CutsceneActor :
					actorObject = (Tools_CutsceneActor_Object*) selectedObject;
					break;
				case TOT_CutsceneTrackPoint :
					actorObject = ((Tools_CutsceneTrackPoint_Object*) selectedObject)->Track->m_pxActorObject;
					break;
			}
		}
		if(actorObject)
		{
			Tools_Cutscene_Timeline::SetActor(actorObject);
		}
	}

	int iScreenWidth = Asura_Viewport::GetWidth();
	int iScreenHeight = Asura_Viewport::GetHeight();
	if(iScreenWidth != g_iPreviousScreenWidth || iScreenHeight != g_iPreviousScreenHeight)
	{
		g_iPreviousScreenWidth = iScreenWidth;
		g_iPreviousScreenHeight = iScreenHeight;

		//screen size has changed , may need to reposition some overlays
		if(CutscenePlaybackOverlayIsOn())
		{
			//re-create overlay by toggling off and on
			ToggleCutscenePlaybackOverlay();
			ToggleCutscenePlaybackOverlay();
		}

	}

}




void Tools_Mode_Cutscene::EarlyRender(void)
{
	
	Tools_CutsceneModeAVI::PreCutsceneRender();

	if(CurrentCutscene && CurrentCutscene->Cutscene->IsPlaying())
	{
		Asura_Cutscene* pxCutscene = CurrentCutscene->Cutscene;
		
		//setup highlight on cutscene actors if needed
		u_int uOutlineColour = 0;
		if(m_bHighlightHierarchyActors)
		{
			float fBright = Asura_Maths::Sine( Asura_Timers::GetLocalUnscaledTime() * 2.0f );
			int iBright = 127 - Asura_Maths::FloatToInt(80.0f*fBright*fBright);
			uOutlineColour = 0xff000000 + (iBright) + (iBright<<8) + (iBright<<16);
		}
		for(int i=0;i<pxCutscene->NumberOfActors;i++)
		{
			if(!pxCutscene->Actors[i]->IsHierarchyActor()) continue;
			Asura_Cutscene_Actor_Hierarchy* pxHierarchy = (Asura_Cutscene_Actor_Hierarchy*)pxCutscene->Actors[i];
			Asura_Animation* pxAnim = pxHierarchy->GetAnimation();
			if(pxAnim)
			{
				pxAnim->SetOutlineColour(uOutlineColour);
			}
		}


		CurrentCutscene->Cutscene->Render();
        //if (Asura_Cutscene_System::ShouldRenderWidescreenBars())
        //{
        //    Asura_Cutscene_System::RenderWidescreenBars();
        //}

	}

	Tools_CutsceneModeAVI::PostCutsceneRender();

//	Asura_Particle_System::Update();
//	Asura_Particle_System::Render();
}

void Tools_Mode_Cutscene::RenderUI(void)
{
//	float fX = 1.0f;
//	float fY = 20.0f;
//	Asura_Text_Output::Print(fX,fY++,0xff00ff00,0,"Redo : %d",CutsceneUndo->GetNumRedo());
//	Asura_Text_Output::Print(fX,fY++,0xff00ff00,0,"Undo : %d",CutsceneUndo->GetNumUndo());
//	Asura_Text_Output::Print(fX,fY++,0xff00ff00,0,"Pos. : %d",CutsceneUndo->GetPosition());

	if(Tools_Cutscene_RecordAnimation::IsRecording())
	{
		float fPrintX = Asura_Viewport::GetWidth()/2.0f;
		float fPrintY = Asura_Viewport::GetHeight()/2.0f;
		Asura_Text_Output::ConvertPixelsToPrintCoords(fPrintX,fPrintY);
		fPrintX-=0.5f;
		fPrintY-=0.5f;

		Asura_Text_Output::Print(fPrintX,fPrintY,0xff00ff00,ASURA_TEXT_FORMAT_CENTREJUSTIFIED,"Recording Animation (Escape to stop)");
	}

}


void Tools_Mode_Cutscene::QuickSave(void)
{
	Tools_SaveAsura::SaveCutscenes();
	if(Tools_SaveAsura::IsEntityModified()) Tools_SaveAsura::SaveEntities();
}

bool Tools_Mode_Cutscene::IsModified()
{
	return Tools_SaveAsura::IsCutscenesModified() || Tools_SaveAsura::IsEntityModified();
}


void Tools_Mode_Cutscene::HandleKeyboard()
{
	if(Asura_Overlay_System::OverlaysHaveKeyboardFocus()) return;
 	if(Asura_UserInput::KeyPressed(ASURA_KEYID_ALT)) return;
 	if(Asura_UserInput::KeyPressed(ASURA_KEYID_CTRL))
 	{
		if(CurrentCutscene && CutscenePlayback_Active)
		{
			if(Asura_UserInput::DebouncedKeyPressed(ASURA_KEYID_LEFT))
			{
				CutscenePlayback_FrameBack();
			}
			if(Asura_UserInput::DebouncedKeyPressed(ASURA_KEYID_RIGHT))
			{
				CutscenePlayback_FrameForward();
			}
			
		}
		if(Asura_UserInput::DebouncedKeyPressed('z'))// 'ctrl+z' for  undo
		{
			RefreshPlayback();
			CutsceneUndo->Undo();
			
		}
		if(Asura_UserInput::DebouncedKeyPressed('y'))// 'ctrl+y' for  redo
		{
			RefreshPlayback();
			CutsceneUndo->Redo();
		}
	}
	else if(Asura_UserInput::KeyPressed(ASURA_KEYID_SHIFT))
	{
		if(CurrentCutscene && CutscenePlayback_Active)
		{
			if(Asura_UserInput::KeyPressed(ASURA_KEYID_LEFT))
			{
				CutscenePlayback_DeltaTime =  -Asura_Timers::GetGameTimeSlice()*2.0f;
			}
			if(Asura_UserInput::KeyPressed(ASURA_KEYID_RIGHT))
			{
				CutscenePlayback_DeltaTime = Asura_Timers::GetGameTimeSlice()*2.0f;
			}
			
		}
	}
	else
	{
		if(CurrentCutscene)
		{
			if(Asura_UserInput::DebouncedKeyPressed(' '))
			{
				if(CutscenePlayback_Active)
				{
					//pause / reset cutscene
					if(CutscenePlayback_Speed == 0)
					{
						AdvanceCutsceneToTime(CurrentCutscene->Cutscene->m_fCutsceneStartTime);
					}
					else
					{
						CutscenePlayback_Speed = 0;
					}
				}
			}
			if(Asura_UserInput::DebouncedKeyPressed('t'))
			{
				ShowTimeline();			
			}
			if(Asura_UserInput::DebouncedKeyPressed('c') && !Tools_Movement::GetFlyingObject())
			{
				Asura_ClientCutscene_Actor_Camera::Disable = !Asura_ClientCutscene_Actor_Camera::Disable;
				RefreshPlayback();//redo cutscene from start so that field of view gets set correctly
			}
			if(Asura_UserInput::DebouncedKeyPressed(ASURA_KEYID_ENTER))
			{
 				//start/resume cutscene playback
				CutscenePlayback_Play();
				
			}
			if(Asura_UserInput::DebouncedKeyPressed('o'))
			{
				CutscenePlayback_HideObjects = !CutscenePlayback_HideObjects;
			}
			if(Asura_UserInput::DebouncedKeyPressed('h'))
			{
				Asura_ClientCutscene_Actor_Hierarchy::DontRender = !Asura_ClientCutscene_Actor_Hierarchy::DontRender;
			}
			if(Asura_UserInput::DebouncedKeyPressed(','))// '<' for  undo
			{
				RefreshPlayback();
				CutsceneUndo->Undo();
				
			}
			if(Asura_UserInput::DebouncedKeyPressed('.'))// '>' for  redo
			{
				RefreshPlayback();
				CutsceneUndo->Redo();
			}

		}

		if (Asura_UserInput::DebouncedKeyPressed(ASURA_KEYID_ESCAPE))
		{
			if(Tools_Cutscene_RecordAnimation::IsRecording())
			{
				Tools_Cutscene_RecordAnimation::AbortRecording();
			}
		}



	}
}


void Tools_Mode_Cutscene::ToggleCutsceneModeOverlay(void)
{
	CutsceneModeOverlayOpen = !CutsceneModeOverlayOpen;

	if (CutsceneModeOverlayOpen)
	{
		CreateCutsceneModeOverlay();
	}
	else
	{
		Asura_Overlay_System::CloseOverlays(TOOLS_OVERLAY_MODE);
	}
}
bool Tools_Mode_Cutscene::CutsceneModeOverlayIsOn(void)
{
	return CutsceneModeOverlayOpen;
}


void Tools_Mode_Cutscene::CreateCutsceneModeOverlay(void)
{
	if(!CutsceneModeOverlayOpen) return;

	Asura_Overlay* pxOverlay;
	pxOverlay = new Asura_Overlay("Cutscene Mode",false,&CutsceneModeOverlayPosition);
	
	Asura_Vector_2 xOffset = Asura_Vector_2(1.0f,3.0f);

	AddCutsceneModeOverlayControls(pxOverlay,xOffset);

	pxOverlay->m_uID = TOOLS_OVERLAY_MODE;


	Asura_Overlay_System::SubmitNewOverlay(pxOverlay,false);
}

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

	pxDialog = new Asura_Function_Dialog("Write FMV Subtitles",xOffset,WriteFMVSubtitleFiles);
	pxDialog->SetHelpText("Writes out files containing the timings of the subtitles of all cutscenes in this level.");
	pxOverlay->AddDialog(pxDialog);
	xOffset.y+=Asura_Dialog::DefaultSpacing();

	pxDialog = new Asura_Function_Dialog("Write FMV Subtitles From Text File",xOffset,WriteFMVSubtitlesFromTextFile);
	pxDialog->SetHelpText("Writes out files containing the timings of the subtitles of all cutscenes specified in the FMV timings file.\nThis is for those cutscenes that are awkward and are going to be glued together outside of tools.");
	pxOverlay->AddDialog(pxDialog);
	xOffset.y+=Asura_Dialog::DefaultSpacing();

	#if ALLOW_CUTSCENE_EXPORT
	pxDialog = new Asura_Function_Dialog("Import Cutscene",xOffset,_ImportCutscene);
	pxDialog->SetHelpText("Imports a cutscene from that has previously been exported from another level.");
	pxOverlay->AddDialog(pxDialog);
	xOffset.y+=Asura_Dialog::DefaultSpacing();
	#endif

	pxDialog = new Asura_Function_Dialog("Import From 3dsMax",xOffset,_ImportCutsceneFromTextFile);
	pxDialog->SetHelpText("Imports details for actors and events from a text file exported from 3dsMax.");
	pxOverlay->AddDialog(pxDialog);
	xOffset.y+=Asura_Dialog::DefaultSpacing();


	pxDialog = new Asura_Function_Dialog("Create Cutscene",xOffset,_CreateCutscene);
	pxDialog->SetHelpText("Create a new empty cutscene.");
	pxOverlay->AddDialog(pxDialog);
	xOffset.y+=Asura_Dialog::DefaultSpacing();


	pxDialog = new Asura_Function_Dialog("Copy Cutscene",xOffset,_CopyCutscene);
	pxDialog->SetHelpText("Create a new cutscene copied from another.");
	pxOverlay->AddDialog(pxDialog);
	xOffset.y+=Asura_Dialog::DefaultSpacing();


	pxDialog = new Asura_Function_Dialog("Record AVI",xOffset,Tools_CutsceneModeAVI::ShowOverlay);
	pxDialog->SetHelpText("Record an AVI of the currently edited cutscene.");
	pxOverlay->AddDialog(pxDialog);
	xOffset.y+=Asura_Dialog::DefaultSpacing();

	pxDialog = new Asura_Function_Dialog("Record screenshots",xOffset,_RecordScreenshots);
	pxDialog->SetHelpText("Record the currently edited cutscene as a series of screenshots.");
	pxOverlay->AddDialog(pxDialog);
	xOffset.y+=Asura_Dialog::DefaultSpacing();

	pxDialog = new Asura_Function_Dialog("Write Report",xOffset,CreateCutsceneReport);
	pxDialog->SetHelpText("Creates a text (.csv) file listing the skins and animations used by cutscenes.");
	pxOverlay->AddDialog(pxDialog);
	xOffset.y+=Asura_Dialog::LargeSpacing();


	pxDialog = new Asura_Tickbox_Dialog("Hide Cutscene Objects",xOffset,&HideCutsceneObjects);
	pxDialog->SetHelpText("Hide the tools objects for the cutscenes.");
	pxOverlay->AddDialog(pxDialog);
	xOffset.y+=Asura_Dialog::DefaultSpacing();

	pxDialog = new Asura_Tickbox_Dialog("Hide Actor Objects",xOffset,&m_bHideActorObjects);
	pxDialog->SetHelpText("Hide the tools objects for the actors.");
	pxOverlay->AddDialog(pxDialog);
	xOffset.y+=Asura_Dialog::DefaultSpacing();

	pxDialog = new Asura_Tickbox_Dialog("Hide Track Objects",xOffset,&m_bHideTrackObjects);
	pxDialog->SetHelpText("Hide the tools objects for the track points.");
	pxOverlay->AddDialog(pxDialog);
	xOffset.y+=Asura_Dialog::DefaultSpacing();

	pxDialog = new Asura_Tickbox_Dialog("Hide Event Objects",xOffset,&HideEventObjects);
	pxDialog->SetHelpText("Hide the tools objects for the events.");
	pxOverlay->AddDialog(pxDialog);
	xOffset.y+=Asura_Dialog::DefaultSpacing();

	pxDialog = new Asura_Tickbox_Dialog("Hide Static Objects",xOffset,&HideStaticObjects);
	pxDialog->SetHelpText("Hide the static objects.");
	pxOverlay->AddDialog(pxDialog);
	xOffset.y+=Asura_Dialog::DefaultSpacing();

#ifdef ASURA_USE_CONTAINER_SYSTEM
	pxDialog = new Asura_Tickbox_Dialog("Hide Container Objects", xOffset, &m_bHideContainerObjects);
	pxDialog->SetHelpText("Hide the container objects.");
	pxOverlay->AddDialog(pxDialog);
	xOffset.y+=Asura_Dialog::DefaultSpacing();
#endif //#ifdef ASURA_USE_CONTAINER_SYSTEM


	pxDialog = new Asura_Tickbox_Dialog("Hide Other Entities",xOffset,&m_bHideOtherEntities);
	pxDialog->SetHelpText("Hides tools objects for objects that aren't concerned with cutscene editing , and that wouldn't normally be visible during cutscene playback.");
	pxOverlay->AddDialog(pxDialog);
	xOffset.y+=Asura_Dialog::DefaultSpacing();

	Asura_Tickbox_Dialog* pxTickDialog = new Asura_Tickbox_Dialog("Disable PFX",xOffset,&m_bDisablePFX);
	pxTickDialog->SetClickedFunction(DisablePFXClicked);
	pxTickDialog->SetHelpText("Disables all PFX effects.");
	pxOverlay->AddDialog(pxTickDialog);
	xOffset.y+=Asura_Dialog::DefaultSpacing();


	pxDialog = new Asura_Tickbox_Dialog("Hide All During Cutscene.",xOffset,&CutscenePlayback_HideObjects);
	pxDialog->SetHelpText("Hides all tools objects during cutscene playback (Except some physical objects that would be visible in game).\nShorcut key : O");
	pxOverlay->AddDialog(pxDialog);
	xOffset.y+=Asura_Dialog::LargeSpacing();

	pxDialog = new Asura_Tickbox_Dialog("Highlight Skins",xOffset,&m_bHighlightHierarchyActors);
	pxDialog->SetHelpText("Draws a pulsating outline around hierarchy actors during cutscene playback to make them stand out.");
	pxOverlay->AddDialog(pxDialog);
	xOffset.y+=Asura_Dialog::LargeSpacing();

	pxDialog = new Asura_Tickbox_Dialog("Move All With Cutscene",xOffset,&Tools_Cutscene_Object::s_bMoveCutsceneMembersWithCutsceneObject);
	pxDialog->SetHelpText("If ticked then moving a cutscene object will cause all members of the cutscene to be moved as well.");
//							"Note : This won't apply to the currently edited cutscene. You should select all the actor/track objects for that instead.");
	pxOverlay->AddDialog(pxDialog);
	xOffset.y+=Asura_Dialog::DefaultSpacing();

}

	

void Tools_Mode_Cutscene::WriteFMVSubtitleFiles()
{
	for(int i=0;i<Asura_Cutscene_System::GetNumberOfCutscenes();i++)
	{
		Asura_Cutscene* pxCutscene = Asura_Cutscene_System::GetCutsceneByIndex(i);

		//does this cutscene have subtitles?
		int iNumSubtitle = 0;
		for(int i=0;i<pxCutscene->NumberOfEvents;i++)
		{
			if(pxCutscene->Events[i]->m_iType == ACET_Subtitle) iNumSubtitle++;
		}
		if(!iNumSubtitle) continue;


		WriteFMVSubtitles(pxCutscene);
	}
}


void Tools_Mode_Cutscene::WriteFMVSubtitles(const Asura_Cutscene* pxCutscene)
{
	//generate the filename
	char szFilename[MAX_PATH];

	Asura_PrintfToBfr(szFilename,"Subtitle\\%s.asr",MAX_PATH,true,pxCutscene->CutsceneName.GetString());
	Tools_FileSystem::EnsureDirectoryExists(szFilename);

	//count the subtitles
	int iNumSubtitle = 0;
	for(int i=0;i<pxCutscene->NumberOfEvents;i++)
	{
		if(pxCutscene->Events[i]->m_iType == ACET_Subtitle) iNumSubtitle++;
	}


	//create the subtitle array for writing
	Asura_FMVSubtitle* pxSubtitle = new Asura_FMVSubtitle[iNumSubtitle];

	//initialise times ,and fill in the text IDs
	for(int i=0;i<iNumSubtitle;i++)
	{
		pxSubtitle[i].m_fStartTime = -1.0f;
		pxSubtitle[i].m_fEndTime = -1.0f;
		pxSubtitle[i].m_uTextID = ASURA_HASH_ID_UNSET;
	}

	//Fill in from the events
	//When playing back AVIs the game won't know about any timescale events , so we need keep track
	//of what the real time is for each event and use that instead of the cutscene times.
	float fTimeScale = 1.0f;
	float fRealTime = 0.0f;
	float fCutsceneTime = 0.0f;

	//time left on previous subtitle
	float fSubtitleTimer = 0.0f;

	int iLine = 0;
	for(int i=0;i<pxCutscene->NumberOfEvents && iLine<iNumSubtitle;i++)
	{
		Asura_Cutscene_Event* pxEvent = pxCutscene->Events[i];
		//keep track of what the real time is for this event
		float fCutsceneTimeDiff = pxEvent->m_fTriggerTime-fCutsceneTime;
		fRealTime += fCutsceneTimeDiff/fTimeScale;
		fCutsceneTime = pxEvent->m_fTriggerTime;

		if(fSubtitleTimer > 0.0f)
		{
			float fTimeUsed;
			if(fCutsceneTimeDiff > fSubtitleTimer)
			{
				//have passed end of subtitle
				fTimeUsed = fSubtitleTimer;
				fSubtitleTimer = 0.0f;
			}
			else
			{
				fTimeUsed = fCutsceneTimeDiff;
				fSubtitleTimer -= fCutsceneTimeDiff;
			}
			//increase the previous subtitle's end time
			pxSubtitle[iLine-1].m_fEndTime += fTimeUsed/fTimeScale;
		}

		if(pxEvent->m_iType == ACET_Subtitle)
		{
			Asura_Cutscene_Event_Subtitle* pxSubtitleEvent = (Asura_Cutscene_Event_Subtitle*) pxEvent;

			pxSubtitle[iLine].m_fStartTime = fRealTime;
			pxSubtitle[iLine].m_fEndTime = pxSubtitleEvent->m_fDuration ?  fRealTime : Asura_Maths::LargeFloat;
			pxSubtitle[iLine].m_uTextID = pxSubtitleEvent->m_uTextHash;

			//track the unused time on the subtitle
			fSubtitleTimer = pxSubtitleEvent->m_fDuration;

			//make sure the end time for the previous line isn't too high
			if(iLine>0)
			{
				if(pxSubtitle[iLine-1].m_fEndTime > pxSubtitle[iLine].m_fStartTime)
				{
					pxSubtitle[iLine-1].m_fEndTime = pxSubtitle[iLine].m_fStartTime;
				}
			}
			iLine++;
		}
		else if(pxEvent->m_iType == ACET_TimeScale)
		{
	
			Asura_Cutscene_Event_TimeScale* pxTimeScaleEvent = (Asura_Cutscene_Event_TimeScale*) pxEvent;
			fTimeScale = pxTimeScaleEvent->m_fTimeScale;
			Asura_Assert(fTimeScale > 0.0f,"");
			
		}
	}

	//finished all the events , so use up any leftover time for last subtitle
	if(fSubtitleTimer > 0.0f)
	{
		pxSubtitle[iLine-1].m_fEndTime += fSubtitleTimer/fTimeScale;
	}


	//Create the saveblock
	Asura_SaveBlock xSaveBlock;
	Asura_Chunk_FMVSubtitles::Create(&xSaveBlock,iNumSubtitle,pxSubtitle);
	xSaveBlock.AddAsuraTerminator();

	if(xSaveBlock.SaveFile(szFilename,false))
	{
		Tools_Timed_Message_System::AddMessage(0x00ff00,"Saved %s.",szFilename);
	}
	else
	{
		Tools_Timed_Message_System::AddMessage(0xff0000,"Failed to write %s.",szFilename);
	}

	delete [] pxSubtitle;
}


void Tools_Mode_Cutscene::WriteFMVSubtitlesFromTextFile()
{
	Asura_Assert(s_pxThis,"");
	
	bool bWarnAboutInvalidStrings = true;

	#ifdef ASURA_USE_DIALOGUE_SYSTEM
	const bool bGetTextFromDialogue = s_pxThis->ShouldGetFMVSubtitleTextFromDialogueLines();
	#else
	const bool bGetTextFromDialogue = false;
	#endif

	Tools_IniFile xIni("ToolsIniFiles\\Cutscene.ini");
	if(!xIni.LoadIniFile())
	{
		Tools_Timed_Message_System::AddMessage(0xff0000,"Failed to load Cutscene.ini.");
		return;
	}

	Asura_String xFilename;
	if(!xIni.GetStringInCurrentSection("FMVTimings",xFilename))
	{
		Tools_Timed_Message_System::AddMessage(0xff0000,"Failed to find FMVTimings entry in Cutscene.ini");
		return;
	}

	const char* pszIniFileName = xFilename.GetString();

	//load the subtitle table
	Tools_TabbedStringTable xTable(pszIniFileName);
	if(!xTable.m_iWidth || xTable.m_iHeight<2)
	{
		Tools_Timed_Message_System::AddMessage(0xff0000,"Failed to load %s",pszIniFileName);
		return;
	}

	//scan the column headings to find the columns we neeed
	int iCutsceneCol = -1;
	int iTextCol = -1;
	int iTimeCol = -1;
	int iDurationCol = -1;
	
	for(int iCol = 0; iCol<xTable.m_iWidth;iCol++)
	{
		const char* pszEntry = xTable.GetEntry(0,iCol);
		if(pszEntry)
		{
			if( !stricmp(pszEntry, "Cutscene") || !stricmp(pszEntry, "FMV") )
			{
				iCutsceneCol = iCol;
			}
			else if(!stricmp(pszEntry,"TextID"))
			{
				iTextCol = iCol;
			}
			else if(!stricmp(pszEntry,"Time"))
			{
				iTimeCol = iCol;
			}
			else if(!stricmp(pszEntry,"Duration"))
			{
				iDurationCol = iCol;
			}
		}
	}

	if(iCutsceneCol == -1)
	{
		Tools_Timed_Message_System::AddMessage(0xff0000,"Failed to find 'Cutscene' or 'FMV' headings in %s",pszIniFileName);
		return;				
	}
	if(iTextCol == -1)
	{
		Tools_Timed_Message_System::AddMessage(0xff0000,"Failed to find 'Text' heading in %s",pszIniFileName);
		return;				
	}
	if(iTimeCol == -1)
	{
		Tools_Timed_Message_System::AddMessage(0xff0000,"Failed to find 'Time' heading in %s",pszIniFileName);
		return;				
	}
	if(iDurationCol == -1)
	{
		Tools_Timed_Message_System::AddMessage(0xff0000,"Failed to find 'Duration' heading in %s",pszIniFileName);
		return;				
	}

	int iLine = 1;
	//scan for the first line with an entry
	while(iLine<xTable.m_iHeight)
	{
		const char* pszEntry;
		pszEntry = xTable.GetEntry(iLine,iCutsceneCol);
		if(pszEntry && strlen(pszEntry)) break;
		iLine++;
	}
	if(iLine == xTable.m_iHeight) return;

	//allocate subtitle array. will be way more than needed per cutscene
	Asura_FMVSubtitle* pxSubtitle          = new Asura_FMVSubtitle[xTable.m_iHeight];
    Asura_String*      pxSubtitleStringIDs = new Asura_String[xTable.m_iHeight];

	do
	{
		Asura_String xCutsceneName = xTable.GetEntry( iLine, iCutsceneCol );
        xCutsceneName.StripWhitespace( Asura_StringUtil::STRIP_BOTH_ENDS );

		int iNumSubtitles = 0;

		//get all the entries for this cutscene
		while(iLine<xTable.m_iHeight)
		{
			pxSubtitle[iNumSubtitles].m_fStartTime = -1.0f;
			pxSubtitle[iNumSubtitles].m_fEndTime = -1.0f;
			pxSubtitle[iNumSubtitles].m_uTextID = ASURA_HASH_ID_UNSET;

			//get text ID
			Asura_String xSubtitleName = xTable.GetEntry( iLine, iTextCol );
            xSubtitleName.StripWhitespace( Asura_StringUtil::STRIP_BOTH_ENDS );

			if( !xSubtitleName.IsBlank() )
			{
                pxSubtitleStringIDs[iNumSubtitles] = xSubtitleName;

                const Asura_Hash_ID uTextID = Asura_GetHashID( xSubtitleName.GetString() );

				pxSubtitle[iNumSubtitles].m_uTextID = uTextID;

				//make sure the hashId matches a valid string
				bool bFoundString = false;
				#ifdef ASURA_USE_DIALOGUE_SYSTEM
				if(bGetTextFromDialogue)
				{
                	if( Asura_Dialogue_Storage::FindLine(uTextID) )
                	{
                	    bFoundString = true;
                	}
				}
				else
				#endif
				{
					if( Asura_HashedLocalisedText::IsValidString(uASURA_HASHEDTEXTPAGE_CUTSCENE, uTextID) )
                	{
                	    bFoundString = true;
                	}
				}
                
                if(!bFoundString)
				{
					if( bWarnAboutInvalidStrings )
					{
						bWarnAboutInvalidStrings = ShowYesNoOverlay( "Warning.\nText ID '%s' doesn't match any loaded string.\n"
                                                                     "Either the text data is out of date or there is a typo in the FMV timings file.\n"
                                                                     "Press No to suppress these warnings.",
                                                                     xSubtitleName.GetString() );
					}
				}
			}
            else
            {
                pxSubtitleStringIDs[iNumSubtitles].MakeNull();
            }

			//start time
			const char* pszEntry = xTable.GetEntry(iLine,iTimeCol);
			if(pszEntry && strlen(pszEntry))
			{
				sscanf(pszEntry,"%f",&pxSubtitle[iNumSubtitles].m_fStartTime);
			}

			//duration
			pszEntry = xTable.GetEntry(iLine,iDurationCol);
			if(pszEntry && strlen(pszEntry))
			{
				float fDuration=0;;
				if(sscanf(pszEntry,"%f",&fDuration)==1 && fDuration!=0.0f)
				{
					pxSubtitle[iNumSubtitles].m_fEndTime = pxSubtitle[iNumSubtitles].m_fStartTime+fDuration;
				}
			}

			//if we have a time and text ID , then accept this entry
			if(pxSubtitle[iNumSubtitles].m_fStartTime>=0.0f && pxSubtitle[iNumSubtitles].m_uTextID)
			{
				if(pxSubtitle[iNumSubtitles].m_fEndTime<=0.0f)
				{
					pxSubtitle[iNumSubtitles].m_fEndTime = Asura_Maths::LargeFloat;
				}

				//make sure the end time for the previous line isn't too high
				if(iNumSubtitles>0)
				{
					if(pxSubtitle[iNumSubtitles-1].m_fEndTime > pxSubtitle[iNumSubtitles].m_fStartTime)
					{
						pxSubtitle[iNumSubtitles-1].m_fEndTime = pxSubtitle[iNumSubtitles].m_fStartTime;
					}
				}
			
				iNumSubtitles++;
			}
						

			//make sure we haven't reached the start of the text for another cutscene
			iLine++;
			pszEntry = xTable.GetEntry(iLine,iCutsceneCol);
			if(pszEntry && strlen(pszEntry)) break;
		}

		//write the subtitle file for this cutscene
		if(iNumSubtitles)
		{
			Tools_Level_Export::EXPORT_TYPE eInitialPlatform = Tools_Level_Export::s_eExportType;
			bool bWasExport = Tools_Level_Export::IsExport();
			Tools_Level_Export::SetIsExport(true);

			
			for(int iPlatform=0; iPlatform < TOOLS_PLATFORM_NUM_PLATFORMS; iPlatform++)
			{

				if(!Tools_Level_Export::IsPlatformSupported((Tools_Level_Export::EXPORT_TYPE)iPlatform)) continue;
								
				
				const char* szPlatformName = Tools_Platform::GetPlatformName(iPlatform);
				const char* szExtension = Tools_Platform::IsBigEndian(iPlatform) ? ASURA_STANDARD_FILE_EXTENSION_BE : ASURA_STANDARD_FILE_EXTENSION_LE;

				Tools_Level_Export::s_eExportType = (Tools_Level_Export::EXPORT_TYPE) iPlatform;
				

				//If taking text from the dialogue system , then we are going to need to create a file per language
				const bool bFilePerLanguage = bGetTextFromDialogue;
				
				for(int iLang=0; iLang < (bFilePerLanguage ? ASURA_LT_L_NUM_LANGUAGES : 1); iLang++)
				{
					//Check a file is wanted for this language , and build the filename to write to
					char szFilename[MAX_PATH];
					if(bFilePerLanguage)
					{
						if(!Tools_Level_Export::Static_IsLanguageAllowed(iLang)) continue;

						const char* szLangCode = Asura_LocalisedText::GetTwoCharCodeForLang((Asura_LocalisedText_Language)iLang);

						Asura_PrintfToBfr(szFilename, "..\\%s_release\\Subtitle\\%s\\%s.%s", MAX_PATH, true,szPlatformName, szLangCode, xCutsceneName.GetString(), szExtension);
					}
					else
					{
						Asura_PrintfToBfr(szFilename, "..\\%s_release\\Subtitle\\%s.%s", MAX_PATH, true,szPlatformName, xCutsceneName.GetString(), szExtension);
					}
					Tools_FileSystem::EnsureDirectoryExists(szFilename);

					Asura_SaveBlock xSaveBlock;

		            // If the string data is in the dialogue system rather than the
		            // localised text system then we need to write out a text page
		            // here.
					if( bGetTextFromDialogue )
		            {
	                    WriteFMVSubtitleTextPageChunk( iLang, iNumSubtitles,
	                                                   pxSubtitle, pxSubtitleStringIDs,
	                                                   &xSaveBlock );
		            }

					Asura_Chunk_FMVSubtitles::Create(&xSaveBlock,iNumSubtitles,pxSubtitle);

					xSaveBlock.AddAsuraTerminator();

					if(xSaveBlock.SaveFile(szFilename,false))
					{
						Tools_Timed_Message_System::AddMessage(0x00ff00,"Saved %s.",szFilename);
					}
					else
					{
						Tools_Timed_Message_System::AddMessage(0xff0000,"Failed to write %s.",szFilename);
					}
				}
			}
			Tools_Level_Export::SetIsExport(bWasExport);
			Tools_Level_Export::s_eExportType = eInitialPlatform;

			
		}

	}while(iLine<xTable.m_iHeight);

    delete[] pxSubtitleStringIDs;
	delete[] pxSubtitle;
}


void Tools_Mode_Cutscene::WriteFMVSubtitleTextPageChunk( int iLanguage, u_int uSubtitleCount,
                                                         const Asura_FMVSubtitle* pxSubtitles, const Asura_String* pxSubtitleStringIDs,
                                                         Asura_SaveBlock* pxSaveBlock )
{
	#ifdef ASURA_USE_DIALOGUE_SYSTEM
    List<Asura_Hash_ID>      xSubtitleHashIDs;
    List<Asura_String>       xSubtitleStringIDs;
    List<Asura_UnicodeChar*> xSubtitleStringData;

    for( u_int u = 0; u < uSubtitleCount; ++u )
    {
        xSubtitleHashIDs.add_entry_end( pxSubtitles[u].m_uTextID );

        xSubtitleStringIDs.add_entry_end( pxSubtitleStringIDs[u] );

        Asura_UnicodeChar* wszTextCopy = NULL;

        const Asura_Dialogue_Line* pxLine = Asura_Dialogue_Storage::FindLine( pxSubtitles[u].m_uTextID );
        if( pxLine )
        {
			const Asura_UnicodeChar* wszText = pxLine->GetTextForLanguage(iLanguage);
			if(!wszText) wszText = pxLine->GetTextForLanguage(ASURA_LT_L_ENGLISH);

			if(wszText)
			{
            	wszTextCopy = Asura_StringUtil::Duplicate( wszText );
			}
        }

        if( !wszTextCopy )
        {
            wszTextCopy = Asura_StringUtil::Duplicate( L"" );
        }

        xSubtitleStringData.add_entry_end( wszTextCopy );
    }

	Asura_Assert( Asura_GetHashID(szASURA_HASHEDTEXTPAGE_CUTSCENE) == uASURA_HASHEDTEXTPAGE_CUTSCENE, "Cutscene text page name does not match hash ID." );
    Asura_Chunk_HashedLocalisedText::Create( szASURA_HASHEDTEXTPAGE_CUTSCENE, xSubtitleHashIDs, xSubtitleStringIDs, xSubtitleStringData, pxSaveBlock );

    while( xSubtitleStringData.size() )
    {
        delete[] xSubtitleStringData.last_entry();
        xSubtitleStringData.delete_last_entry();
    }
	#else
	Asura_Assert(0,"Dialogue system not activated");		
	#endif
}


void Tools_Mode_Cutscene::ToggleCutsceneEditOverlay(void)
{
	CutsceneEditOverlayOpen = !CutsceneEditOverlayOpen;

	if (CutsceneEditOverlayOpen)
	{
		CreateCutsceneEditOverlay();
	}
	else
	{
		Asura_Overlay_System::CloseOverlays(TOOLS_OVERLAY_CUTSCENE);
	}
}
bool Tools_Mode_Cutscene::CutsceneEditOverlayIsOn(void)
{
	return CutsceneEditOverlayOpen;
}

void Tools_Mode_Cutscene::CreateCutsceneEditOverlay(void)
{
	if(!CutsceneEditOverlayOpen) return;
	if(!CurrentCutscene) return;
	

	Asura_Overlay* newOverlay;
	newOverlay = new Asura_Overlay("Cutscene",false,&CutsceneEditOverlayPosition);
	
	//Asura_Dialog* newDialog;
	Asura_Vector_2 offset = Asura_Vector_2(1.0f,3.0f);

	CurrentCutscene->AddOverlayControls(newOverlay,offset);

	newOverlay->m_uID = TOOLS_OVERLAY_CUTSCENE;

	Asura_Overlay_System::SubmitNewOverlay(newOverlay,false);

	CurrentCutscene->UpdateOverlayLabels();
}

void Tools_Mode_Cutscene::ToggleCutscenePlaybackOverlay(void)
{
	CutscenePlaybackOverlayOpen = !CutscenePlaybackOverlayOpen;

	if (CutscenePlaybackOverlayOpen)
	{
		CreateCutscenePlaybackOverlay();
	}
	else
	{
		Asura_Overlay_System::CloseOverlays(TOOLS_OVERLAY_CUTSCENE_PLAYBACK);
		Asura_Overlay_System::CloseOverlays(TOOLS_OVERLAY_CUTSCENE_PLAYBACKSLIDER);
	}
}
bool Tools_Mode_Cutscene::CutscenePlaybackOverlayIsOn(void)
{
	return CutscenePlaybackOverlayOpen;
}


void Tools_Mode_Cutscene::CreateCutscenePlaybackOverlay(void)
{
	if(!CutscenePlaybackOverlayOpen) return;
	if(!CurrentCutscene) return;


	//start creating the overlay
	Asura_Overlay* pxOverlay;
	CutscenePlaybackOverlayPosition.x = 60;
	CutscenePlaybackOverlayPosition.y = (Asura_Viewport::GetHeight()/14.0f)-5.0f;
	pxOverlay = new Asura_Overlay("Cutscene Playback",false,&CutscenePlaybackOverlayPosition);
	pxOverlay->m_bOnPanel = true;

	Asura_Dialog* pxDialog;
	Asura_Vector_2 xOffset;


	
	xOffset.x = 0.0f;
	xOffset.y = 2.0f;
	pxDialog = new Asura_Tickbox_Dialog("Use Recorded Bink",xOffset,&m_bUseRecordedWavFile);
	pxDialog->SetHelpText("Uses the pre-recorded bink file when viewing the cutscene in tools.\nIf not set , then the individual files defined in CutsceneText.txt will be used instead.");
	pxOverlay->AddDialog(pxDialog);

	xOffset.y = 3.5f;
	pxDialog = new Asura_Tickbox_Dialog("Disable Camera",xOffset,&Asura_ClientCutscene_Actor_Camera::Disable,&CutscenePlayback_Refresh);
	pxDialog->SetHelpText("Disables the camera actor while viewing the cutscene , allowing the camera to be moved using normal movement keys.");
	pxOverlay->AddDialog(pxDialog);

	xOffset.x = 14.0f;
	pxDialog = new Asura_Function_Dialog("",xOffset,_SetTimerMode);
	pxDialog->SetHelpText(	"Sets times to be displayed in one of these modes :\n"
							"25 FPS - Displays time as frame numbers at 25 FPS\n"
							"30 FPS - Displays time as frame numbers at 30 FPS\n"
							"Seconds - Displays time in seconds\n");
	pxDialog->m_iControlID = DialogID_TimerMode;
	pxOverlay->AddDialog(pxDialog);



	xOffset.x = 25.0f;
	pxDialog = new Tools_CutsceneTime_Dialog("Time",xOffset,&CurrentCutscene->Cutscene->Timer,0,1000,0.1f,&CutscenePlayback_Refresh);
	pxDialog->SetHelpText("Current time in cutscene.");
	pxOverlay->AddDialog(pxDialog);

	//values for the buttons
	Asura_Vector_2 ButtonSizes[7] =
	{
		Asura_Vector_2(30.0f,24.0f),		
		Asura_Vector_2(24.0f,24.0f),		
		Asura_Vector_2(24.0f,24.0f),		
		Asura_Vector_2(24.0f,24.0f),		
		Asura_Vector_2(24.0f,24.0f),		
		Asura_Vector_2(24.0f,24.0f),		
		Asura_Vector_2(30.0f,24.0f),		
	};
	Asura_Vector_2 ButtonPositions[7] =
	{
		Asura_Vector_2(0.0f,0.0f),		
		Asura_Vector_2(30.0f,0.0f),		
		Asura_Vector_2(54.0f,0.0f),		
		Asura_Vector_2(78.0f,0.0f),		
		Asura_Vector_2(102.0f,0.0f),		
		Asura_Vector_2(126.0f,0.0f),		
		Asura_Vector_2(150.0f,0.0f),		
	};

	void (*ButtonFunctions[7])(void) =
	{
		_CutscenePlayback_Rewind,
		_CutscenePlayback_FrameBack,
		_CutscenePlayback_Play,
		_CutscenePlayback_Pause,
		_CutscenePlayback_Stop,
		_CutscenePlayback_FrameForward,
		_CutscenePlayback_FastForward,
	};

	const char* apszHelpText[7] =
	{
		"Rewind at x1,x2 or x4 speed.",
		"Rewind cutscene 1/25th of a second , and pause.",
		"Starts cutscene playing at normal speed.",
		"Pause cutscene.",
		"Stop cutscene playback.",
		"Advances cutscene 1/25th of a second , and pauses.",
		"Fastforward at x1,x2 or x4 speed.",
	};
	
	//create all the buttons
	xOffset.x = 23.0f;
	xOffset.y = 1.25f;

	for(int i=0;i<7;i++)
	{
		pxDialog = new Asura_Button_Dialog(xOffset,ButtonsTextureIndex,256,ButtonPositions[i],ButtonSizes[i],ButtonFunctions[i]);
		pxDialog->SetHelpText(apszHelpText[i]);
		pxOverlay->AddDialog(pxDialog);
		xOffset.x += ButtonSizes[i].x/10.0f;
	}

	//submit the overlay
	pxOverlay->m_uID = TOOLS_OVERLAY_CUTSCENE_PLAYBACK;
	Asura_Overlay_System::SubmitNewOverlay(pxOverlay,false);

	//create an overlay for the time slider
	static Asura_Vector_2 SliderPosition;
	SliderPosition.x = 0;
	SliderPosition.y = (Asura_Viewport::GetHeight()/14.0f)-8.0f;
	pxOverlay = new Asura_Overlay("Cutscene Control",false,&SliderPosition);
	pxOverlay->m_bOnPanel = true;

	//Add a divider at the top.
	//Just there to fill in the space that is grabbed when resizing the overlay
	xOffset.x = 0.0f;
	xOffset.y = 0.25;
	pxDialog = new Asura_Divider_Dialog(xOffset);
	pxOverlay->AddDialog(pxDialog);
	

	//create some icons
	xOffset.x = 0.1f;
	xOffset.y = 0.3f;
	Asura_Icon_Dialog* pxIcon = new Asura_Icon_Dialog(xOffset, Tools_Dialog::GetIconTexture(), TOOLS_ICON_A+'S'-'A');
	pxIcon->SetCheckboxBool(&Tools_Cutscene_Timeline::s_bUseSnap);
	pxIcon->SetHelpText("Enables snapping when dragging within the timeline");
	pxOverlay->AddDialog(pxIcon);

	
	//create the time slider control
	xOffset.x = Tools_Cutscene_Timeline::GetTimelineBarMinX() / ASURA_TEXT_OUTPUT_FONT_WIDTH;
	xOffset.y = 0.5f;
	float slideWidth = (Asura_Viewport::GetWidth()/ASURA_TEXT_OUTPUT_FONT_WIDTH) - xOffset.x - Asura_Scrollbar_Gadget::GetDefaultThickness(false);
	pxDialog = new Tools_CutsceneTimeSlider_Dialog(xOffset,&CutscenePlayback_Timer,&Tools_Cutscene_Timeline::MinVisibleTime,&Tools_Cutscene_Timeline::MaxVisibleTime,slideWidth,&CutscenePlayback_TimesliderModified);
	pxOverlay->AddDialog(pxDialog);

	//create the timeline control
	xOffset.x = 0;
	xOffset.y = 1.5f;
	pxDialog = new Tools_CutsceneTimeline_Dialog(xOffset);
	pxDialog->m_iControlID = Asura_GetHashID("TimelineBar");
	pxOverlay->AddDialog(pxDialog);

	//submit the overlay
	pxOverlay->SetResizeUsingOverlayBorder(true);
	pxOverlay->m_uID = TOOLS_OVERLAY_CUTSCENE_PLAYBACKSLIDER;
	Asura_Overlay_System::SubmitNewOverlay( pxOverlay, false, true, false );

	UpdateCutscenePlaybackOverlay();
}

void Tools_Mode_Cutscene::UpdateCutscenePlaybackOverlay()
{
	//find the overlay
	Asura_Overlay* pxOverlay = Asura_Overlay_System::GetOverlay(TOOLS_OVERLAY_CUTSCENE_PLAYBACK);
	if(!pxOverlay) return;

	//find the timer mode dialog
	Asura_Dialog* pxDialog = pxOverlay->GetDialog(DialogID_TimerMode);
	if(!pxDialog) return;

	switch(s_eTimerMode)
	{
		case CUTSCENETIMER_SECONDS :
			pxDialog->UpdateLabel("Timer : Seconds");
			break;

		case CUTSCENETIMER_25FPS :
			pxDialog->UpdateLabel("Timer : 25 FPS");
			break;

		case CUTSCENETIMER_30FPS :
			pxDialog->UpdateLabel("Timer : 30 FPS");
			break;
	}
	
}


void Tools_Mode_Cutscene::CreateCutscene()
{
	CreateCutscene( NULL, -1 );
}

Tools_Cutscene_Object* Tools_Mode_Cutscene::CreateCutscene( const char* szName, int iType )
{
	//get a name for the cutscene
	Asura_String xName;
	if (szName)
	{
		xName = szName;
		Asura_Assert(!Asura_Cutscene_System::GetCutscene(xName.GetString()), "should have checked for unique name");
	}
	else
	{
		GetString_Overlay overlay("Enter cutscene name","");
		if(!overlay.DoModal()) return 0;

		xName = overlay.GetString();
		if(!xName.GetLength()) return 0;
	}

	//make sure a cutscene doesn't already exist with this name
	if(Asura_Cutscene_System::GetCutscene(xName.GetString()))
	{
		Tools_Timed_Message_System::AddMessage(0xffffff,"That name is already in use");
		return 0;
	}

	if (iType < 0)
	{
		iType = SelectCutsceneType();
	}
	if(iType < 0) return 0;
	
	//create the cutscene
	Asura_Cutscene* cutscene = AllocateCutscene(xName, iType);
	if(!cutscene) return 0;
	Asura_Cutscene_System::AddCutscene(cutscene);
   	
	//set its position
	cutscene->Position = *Asura_Camera::GetPosition();

	//project specific setting of defaults for the cutscene
	SetCutsceneDefaults(cutscene);

	//create a tools cutscene object for it
	Tools_Cutscene_Object* pxCutsceneObject = CreateCutsceneObject(cutscene);
	Tools_Object_System::SelectObject(pxCutsceneObject);

	Tools_SaveAsura::SetCutscenesModified();
	Tools_Timed_Message_System::AddMessage(0xffffff,"Cutscene %s created",xName.GetString());

	EditCutscene(pxCutsceneObject);

	//update cutscene controller objects
	Tools_Entity_CutsceneController_Object::UpdateCutsceneControllersFromCutscenes();

	return pxCutsceneObject;
}

void Tools_Mode_Cutscene::CopyCutscene()
{
	//see if a cutscen object is selected
	Asura_Cutscene* pxCutsceneToCopy = 0;
	if(Tools_Object_System::SelectedObject)
	{
		if(IsCutsceneObject(Tools_Object_System::SelectedObject->Type))
		{
			pxCutsceneToCopy = ((Tools_Cutscene_Object*)Tools_Object_System::SelectedObject)->Cutscene;
		}
	}
	if(!pxCutsceneToCopy)
	{
		Tools_Timed_Message_System::AddMessage(0xffffff,"Please select a cutscene to copy first");
		return;
	}

	//get a name for the cutscene
	GetString_Overlay overlay("Enter name for copy of cutscene","");
	if(!overlay.DoModal()) return;

	Asura_String name = overlay.GetString();
	if(!name.GetLength()) return;


	//make sure a cutscene doesn't already exist with this name
	if(Asura_Cutscene_System::GetCutscene(name.GetString()))
	{
		Tools_Timed_Message_System::AddMessage(0xffffff,"That name is already in use");
		return;
	}

	//create the cutscene
	Asura_Cutscene* cutscene = AllocateCutscene(name,pxCutsceneToCopy->GetCutsceneType());
	if(!cutscene) return;
	Asura_Cutscene_System::AddCutscene(cutscene);

	//do the copying
	cutscene->Copy(pxCutsceneToCopy);
   	
	//set its position
	cutscene->Position = *Asura_Camera::GetPosition();

	//create a tools cutscene object for it
	Tools_Cutscene_Object* pxCutsceneObject = CreateCutsceneObject(cutscene);
	Tools_Object_System::SelectObject(pxCutsceneObject);

	Tools_SaveAsura::SetCutscenesModified();
	Tools_Timed_Message_System::AddMessage(0xffffff,"Cutscene %s created",name.GetString());

	EditCutscene(pxCutsceneObject);


	//update cutscene controller objects
	Tools_Entity_CutsceneController_Object::UpdateCutsceneControllersFromCutscenes();
}


int Tools_Mode_Cutscene::SelectCutsceneType()
{
	return ACT_StandardCutscene;// only one cutscene type in standard cutscene tools
}

Asura_Cutscene* Tools_Mode_Cutscene::AllocateCutscene(Asura_String& name,int cutsceneType)
{
	//I think there should be no need to have a seperate switch statement of cutscene types here , we should
	//be able to use the Asura_Cutscene version instead. RAR 07/05/04
	return Asura_Cutscene::AllocateCutscene(cutsceneType,name);
	
	/*
	if(cutsceneType == ACT_StandardCutscene)
	{
		return new Asura_Cutscene(name);
	}
	return 0;
	*/
}

void Tools_Mode_Cutscene::SetCutsceneDefaults(Asura_Cutscene* pxCutscene)
{
	//Used to set cutscene FOV here ,but probably not needed now its in
	//the camera actor. RAR 11/01/2006
}

Tools_Cutscene_Object* Tools_Mode_Cutscene::CreateCutsceneObject(Asura_Cutscene* cutscene)
{
	return new Tools_Cutscene_Object(cutscene);
}


void Tools_Mode_Cutscene::EditCutscene(class Tools_Cutscene_Object* cutscene)
{
	if(cutscene == CurrentCutscene) return;
	StopPlayback();
	Tools_Cutscene_RecordAnimation::AbortRecording();

	Tools_Cutscene_Timeline::Invalidate();
	Tools_Cutscene_Timeline::SetCutscene(cutscene);
	if(CurrentCutscene)
	{
		CurrentCutscene->FinishCutsceneEdit();
		Tools_Animation_Files::SetCutsceneDirectory(0);

	}

	CurrentCutscene = cutscene;

	UpdateLinkedCutscenes();

	if(CurrentCutscene)
	{
		//create objects for this cutscene
		CurrentCutscene->CreateChildObjects();
		CurrentCutscene->Cutscene->Timer = 0.0f;

		//make sure actors are properly sorted
		CurrentCutscene->SortActors();

		
		CreateCutsceneEditOverlay();
		CreateCutscenePlaybackOverlay();

		Tools_Animation_Files::SetCutsceneDirectory(CurrentCutscene->GetCutsceneDirectory());

		CurrentCutscene->StartCutsceneEdit();
	
		Tools_Timed_Message_System::AddMessage(0xffffff,"Editing '%s'",CurrentCutscene->GetName());
	}

	if(CutsceneUndo) CutsceneUndo->FlushUndoBuffer();


}

void Tools_Mode_Cutscene::EditCutsceneDirectory()
{
	Asura_Assert(CurrentCutscene,"");

	CurrentCutscene->EditCutsceneDirectory();
	Tools_Animation_Files::SetCutsceneDirectory(CurrentCutscene->GetCutsceneDirectory());

	//update the overlay
	CurrentCutscene->UpdateOverlayLabels();


}


void Tools_Mode_Cutscene::EditCutsceneName()
{
	Asura_Assert(CurrentCutscene,"");

	//This requires both entity and cutscene files to be available
	if(!Tools_SaveAsura::CanSaveEntity())
	{
		YesNo_Overlay xOverlay("Warning. Cutscene renaming requires the entity file to be writeable. Continue anyway?");
		if(!xOverlay.DoModal()) return;
	}
		

	Asura_String xOldCutsceneName = CurrentCutscene->GetName();
    
    if (CurrentCutscene->Rename())
    {
	    //update the overlay
        Asura_Overlay_System::CloseOverlays(TOOLS_OVERLAY_CUTSCENE);
        CreateCutsceneEditOverlay();

        // update cutscene controllers
        Asura_ServerEntity_CutsceneController::CutsceneRenamed(xOldCutsceneName, CurrentCutscene->GetName());
		Tools_SaveAsura::SetEntityModified();

    }
}

void Tools_Mode_Cutscene::ExportCutscene()
{
	Asura_Assert(CurrentCutscene,"");
	CurrentCutscene->ExportCutscene();
}


void Tools_Mode_Cutscene::CreateActor()
{
	Asura_Assert(CurrentCutscene,"");
	CurrentCutscene->CreateActor();
}

void Tools_Mode_Cutscene::CopyActor()
{
	Asura_Assert(CurrentCutscene,"");
	CurrentCutscene->CopyActor();
}

void Tools_Mode_Cutscene::DuplicateSelectedActors()
{
	Asura_Assert(CurrentCutscene,"");
	CurrentCutscene->DuplicateSelectedActors();
}


void Tools_Mode_Cutscene::CreateCutsceneEvent()
{
	Asura_Assert(CurrentCutscene,"");
	CurrentCutscene->CreateCutsceneEvent(true);
}



void Tools_Mode_Cutscene::EditEvent()
{
	Asura_Assert(CurrentCutscene,"");

	if(!CurrentCutscene->EventList.size())
	{
		Tools_Timed_Message_System::AddMessage(0xff0000,"This cutscene doesn't have any events.");
		return;
	}

	if(CurrentCutscene->SelectedEvent)
	{
		CurrentCutscene->SelectEvent(CurrentCutscene->SelectedEvent);
	}
	else
	{
		CurrentCutscene->SelectEvent(CurrentCutscene->EventList.first_entry());
	}
}


void Tools_Mode_Cutscene::RecordScreenshots()
{
	if(!CurrentCutscene)
	{
		Tools_Timed_Message_System::AddMessage(0xff0000,"Must be editing a cutscene first");
		return;
	}
	
	//ask what framerate is wanted
	GetNumber_Overlay xOverlay("Select desired frame rate", 60.0f, 5.0f, 120.0f, "%.1f");

	if(!xOverlay.DoModal()) return;
	m_fScreenshotsFrameRate = xOverlay.GetNumber();
	m_bBeginScreenshotRecording = true;
	m_bRecordingScreenshots = false;
}

void Tools_Mode_Cutscene::BeginRecordScreenshots()
{
	if(!CurrentCutscene)
	{
		Tools_Timed_Message_System::AddMessage(0xff0000,"Must be editing a cutscene first");
		return;
	}

	//go to the start of the cutscene
	AdvanceCutsceneToTime(CurrentCutscene->Cutscene->m_fCutsceneStartTime);
	Asura_Cutscene::DisableSubtitles(true);//don't want subtitles in screenshot version

	//ensure screenshots directory exists
	CreateDirectory("Screenshots",0);

	Tools_System::SetRecordScreenshots(true);
	m_bBeginScreenshotRecording = false;
	m_bRecordingScreenshots = true;

	Asura_Timers::SetFixedFrameRate(m_fScreenshotsFrameRate);

	Tools_Timed_Message_System::AddMessage(0x00ff00,"Writing screenshots");
}

void Tools_Mode_Cutscene::EndRecordScreenshots()
{
	Tools_System::SetRecordScreenshots(false);
	m_bRecordingScreenshots = false;

	// ARC - 25 February 2009 08:54:52 - SetFixedFrameRate() treats negative numbers as 0, but if you pass 0.0f, you get a spurious "potential divide by 0" warning
	Asura_Timers::SetFixedFrameRate(-1.0f);

	Tools_Timed_Message_System::AddMessage(0x00ff00,"Finished screenshots");
}

void Tools_Mode_Cutscene::RecordWavFile()
{
	if(!CurrentCutscene) return;
	
	StopPlayback();
	CurrentCutscene->Cutscene->LoadFiles();
	CurrentCutscene->RecordWavFile();

}

void Tools_Mode_Cutscene::PickWavFile()
{
	if(!CurrentCutscene) return;
	
	CurrentCutscene->PickWavFile();

}


void Tools_Mode_Cutscene::StartPlayback()
{
	StopPlayback();
	ActivateParticleGenerators(true);

	Asura_Cutscene::DisableSoundTrack(!m_bUseRecordedWavFile);

	CurrentCutscene->Cutscene->m_fAudioStartTime = 0.0f;

	Asura_Assert(CurrentCutscene,"");
	CurrentCutscene->Cutscene->LoadFiles();
	CurrentCutscene->Cutscene->Initialise();

	//re-enable all events
	Asura_Cutscene_System::ResetEnabledEventFlags();

    // If it failed to start playing for whatever reason, give up at this
    // point, so we don't go getting our state confused below (in particular,
    // setting playback as active when it's not.)
    if( !CurrentCutscene->Cutscene->IsPlaying() ) { return; }

	Asura_Texture_Management::LoadTextures();

	//CutscenePlayback_Paused = false;
	//CutscenePlayback_Reversed = false;
	CutscenePlayback_Refresh = false;
	CutscenePlayback_DeltaTime = 0;
	CutscenePlayback_Timer = 0;
	//CutscenePlayback_HideObjects = true;
	//Asura_Cutscene_Actor_Camera::Disable = false;

	//show the cutscene playback overlay
	CutscenePlayback_Active = true;
//	CreateCutscenePlaybackOverlay();
}

void Tools_Mode_Cutscene::StopPlayback()
{
	if(CurrentCutscene && CurrentCutscene->Cutscene->IsPlaying())
	{
		CutscenePlayback_Active = false;
		
		CurrentCutscene->Cutscene->Finish();
		CutscenePlayback_Refresh = false;

		CurrentCutscene->UpdateAfterCutscene();

		//show the various cutscene objects
		Tools_Object_System::SetHideAll(ObjectHideFlag_Mode,false);

		//remove any timescale changes from cutscene playback
		Asura_Timers::SetGameTimeScale(Tools_System::UserSetGameTimeScale);
		Tools_Render_Options::UpdateFieldOfView();

		//turn off any object flying
		Tools_Movement::SetFlyingObject(0);

        Asura_Entity_System::PruneEntityLists();

		ActivateParticleGenerators(false);

		//make sure any fade effects are cancelled
		Asura_FullScreenSFX_System::CancelFade();

		//re-enable all events
		Asura_Cutscene_System::ResetEnabledEventFlags();

		#ifdef ASURA_USE_DIALOGUE_SYSTEM
		Asura_Dialogue_System::StopAll();
		#endif

		//reset any phonons
		Asura_Phonon_System::ResetPositions();

		Asura_StaticMessage_System::ResetDelayedMessages();

		//update the active state of any phonons
		for(Asura_Hashed_List_It<Asura_ServerEntity_SoundController> xIt(Asura_ServerEntity_SoundController::GetList());!xIt.Done();xIt.Next())
		{
			Asura_ServerEntity_SoundController* pxServerEntity = xIt.GetCurrent();
			Asura_ClientEntity_SoundController* pxClientEntity = Asura_ClientEntity_SoundController::FindEntity(pxServerEntity->GetGuid());

			pxClientEntity->Activate(pxServerEntity->IsActive());
		}


	}
	Asura_Cutscene::DisableSubtitles(false);//reneable , in case they have been disabled by AVI rendering
	Asura_Cutscene::DisableSoundTrack(false);


}

void Tools_Mode_Cutscene::RefreshPlayback()
{
	if(CurrentCutscene && CurrentCutscene->Cutscene->IsPlaying())
	{
		s_pxThis->CutscenePlayback_Refresh = true;
		CurrentCutscene->Cutscene->Finish();
		Asura_Entity_System::PruneEntityLists();
	}
	Tools_Cutscene_Timeline::Invalidate();

}

void Tools_Mode_Cutscene::OnRestart()
{
	//remove any timescale changes from cutscene playback
	Asura_Timers::SetGameTimeScale(Tools_System::UserSetGameTimeScale);

	//and various SFX
	Tools_Render_Options::UpdateFieldOfView();
	Asura_FullScreenSFX_System::CancelFade();

	//Completely kill off PFX effects to avoid build up of particles
	Asura_PFX_System::ClearAllEffects();
	//force restart of particle generators
	ActivateParticleGenerators(false);
	ActivateParticleGenerators(true);

	#ifdef ASURA_USE_DIALOGUE_SYSTEM
	Asura_Dialogue_System::StopAll();
	#endif

	Asura_StaticMessage_System::ResetDelayedMessages();


	if(Tools_CutsceneModeAVI::IsRecording() && !Tools_CutsceneModeAVI::IsDisablingPFX())
	{
		
		Asura_Timers::HandlePrimaryTimer();
		//simulate the PFX effects for a few seconds before starting the recording. RAR 19/05/2006
		const int iNumUpdates = Asura_Maths::FloatToInt(2.0f/Asura_Timers::GetGameTimeSlice());
		for(int i=0;i<iNumUpdates;i++)
		{
		
			for(Asura_Hashed_List_It<Asura_ClientEntity_PFX_Effect> xIt(Asura_ClientEntity_PFX_Effect::GetList()) ;!xIt.Done(); xIt.Next() )
			{
				Asura_ClientEntity_PFX_Effect* pxGenerator = xIt.GetCurrent();
				if(pxGenerator->IsDestroyed()) continue;

				if(pxGenerator->ShouldUpdate())
				{
					pxGenerator->Update();
				}
			}

			Asura_PFX_System::Update();		

		}
	}

	//update the active state of any phonons
	for(Asura_Hashed_List_It<Asura_ServerEntity_SoundController> xIt(Asura_ServerEntity_SoundController::GetList());!xIt.Done();xIt.Next())
	{
		Asura_ServerEntity_SoundController* pxServerEntity = xIt.GetCurrent();
		Asura_ClientEntity_SoundController* pxClientEntity = Asura_ClientEntity_SoundController::FindEntity(pxServerEntity->GetGuid());

		pxClientEntity->Activate(pxServerEntity->IsActive());
	}

	
}

void Tools_Mode_Cutscene::CutscenePlayback_Rewind()
{
	if(!CutscenePlayback_Active) StartPlayback();
	//go x1,x2,or x4 speed in reverse
	if(CutscenePlayback_Speed>-1.0f) CutscenePlayback_Speed = -1.0f;
	else if(CutscenePlayback_Speed>-2.0f) CutscenePlayback_Speed = -2.0f;
	else if(CutscenePlayback_Speed>-4.0f) CutscenePlayback_Speed = -4.0f;
	else CutscenePlayback_Speed = -1.0f;
}

void Tools_Mode_Cutscene::CutscenePlayback_FrameBack()
{
	if(!CutscenePlayback_Active) StartPlayback();

	CutscenePlayback_Speed = 0;
	if(s_eTimerMode == CUTSCENETIMER_25FPS || s_eTimerMode == CUTSCENETIMER_30FPS)
	{
		//if showing time in frames , try to make sure timer stays on a frame boundary
		int iFPS = (s_eTimerMode == CUTSCENETIMER_25FPS) ? 25 : 30;
		int iNewFrame = Asura_Maths::FloatToInt_Near(CurrentCutscene->Cutscene->Timer*iFPS)-1;
		float fNewTime = (float)iNewFrame/(float)iFPS;

		CutscenePlayback_DeltaTime = fNewTime - CurrentCutscene->Cutscene->Timer;
		
	}
	else
	{
		CutscenePlayback_DeltaTime = -.04f;
	}
}

void Tools_Mode_Cutscene::CutscenePlayback_Play()
{
	if(CurrentCutscene->Cutscene->Timer == 0.0f)
	{
		//If the cutscene is paused at the start , stop and restart the cutscene so that sounds
		//at the start of the cutscene play correctly
		StopPlayback();
	}

	if(!CutscenePlayback_Active) StartPlayback();
	CutscenePlayback_Speed = 1.0f;
}

void Tools_Mode_Cutscene::CutscenePlayback_Pause()
{
	if(!CutscenePlayback_Active) StartPlayback();
	CutscenePlayback_Speed = 0.0f;
}

void Tools_Mode_Cutscene::CutscenePlayback_Stop()
{
	StopPlayback();
}

void Tools_Mode_Cutscene::CutscenePlayback_FrameForward()
{
	if(!CutscenePlayback_Active) StartPlayback();
	
	CutscenePlayback_Speed = 0;
	if(s_eTimerMode == CUTSCENETIMER_25FPS || s_eTimerMode == CUTSCENETIMER_30FPS)
	{
		//if showing time in frames , try to make sure timer stays on a frame boundary
		int iFPS = (s_eTimerMode == CUTSCENETIMER_25FPS) ? 25 : 30;
		int iNewFrame = Asura_Maths::FloatToInt_Near(CurrentCutscene->Cutscene->Timer*iFPS)+1;
		float fNewTime = (float)iNewFrame/(float)iFPS;

		CutscenePlayback_DeltaTime = fNewTime - CurrentCutscene->Cutscene->Timer;
		
	}
	else
	{
		CutscenePlayback_DeltaTime = .04f;
	}


}

void Tools_Mode_Cutscene::CutscenePlayback_FastForward()
{
	if(!CutscenePlayback_Active) StartPlayback();
	//go x1,x2,or x4 speed
	if(CutscenePlayback_Speed<1.0f) CutscenePlayback_Speed = 1.0f;
	else if(CutscenePlayback_Speed<2.0f) CutscenePlayback_Speed = 2.0f;
	else if(CutscenePlayback_Speed<4.0f) CutscenePlayback_Speed = 4.0f;
	else CutscenePlayback_Speed = 1.0f;
}



void Tools_Mode_Cutscene::AdvanceCutsceneToTime(float time)
{
	//start cutscene playing if need be
	if(!CutscenePlayback_Active)
	{
		StartPlayback();
	}
	//set the timer
	CurrentCutscene->Cutscene->Timer = time;
	RefreshPlayback();
	//pause at this point
	CutscenePlayback_Speed = 0.0f;

}
void Tools_Mode_Cutscene::ShowTimeline()
{
	if(CurrentCutscene)
	{
		CurrentCutscene->Cutscene->LoadFiles();
		Tools_Cutscene_Timeline::ShowTimeline(CurrentCutscene);
	}
	else
	{
		Tools_Timed_Message_System::AddMessage(0xffffff,"Must be editing a cutscene to do this");
	}
}

void Tools_Mode_Cutscene::UpdateObjectVisibilities()
{
	bool bMessageEditing = Tools_Object_System::IsChoosingObject() || Tools_Object_System::IsMessageEditing();
	bool bHideForCutscene = false;
	
	if(CurrentCutscene && CurrentCutscene->Cutscene->IsPlaying())
	{
		bHideForCutscene = CutscenePlayback_HideObjects;
	}

	if(bMessageEditing)
	{
		//make sure cutscene controller object positions are up to date
		Tools_Entity_CutsceneController_Object::UpdateCutsceneControllersFromCutscenes();
	}

	for(LIF<Tools_Object*> xObjectLIF(&Tools_Object_System::ObjectList);!xObjectLIF.done();xObjectLIF.next())
	{
		xObjectLIF()->SetHiddenFlag(ObjectHideFlag_Cutscene,bHideForCutscene && xObjectLIF()->ShouldHideDuringCutscene());
		
		if(IsCutsceneObject(xObjectLIF()->Type))
		{
			//tools objects used by the cutscene system should  be generally visible ,
			//but hidden during message editing
			xObjectLIF()->SetHiddenFlag(ObjectHideFlag_Mode,bMessageEditing);
		}
		else if(xObjectLIF()->ShouldHideDuringCutscene() && !xObjectLIF()->IsTemporaryEditObject())
		{
			switch(xObjectLIF()->Type)
			{
				case TOT_VolumeEdit :
				case TOT_VolumeGroupEdit : 
				case TOT_PositionEdit :
				case TOT_OrientedVolumeEdit :
				case TOT_CutsceneActor :
				case TOT_CutsceneTrackPoint :
				case TOT_CutsceneEvent :
					//utility objects should generally be visible 
					//(except during cutscene playback - taken care of above)
					break;
				case TOT_Entity_CutsceneController :
					//the cutscene controller object should only be visible during message editing
					//to avoid confusion with the cutscene object
					xObjectLIF()->SetHiddenFlag(ObjectHideFlag_Mode,!bMessageEditing);
					break;

				default :
					//everything else should be entity (or similar) type objects that wouldn't normally
					//be visible during cutscene playback. Hide these if flagged to hide 'other entities'
					xObjectLIF()->SetHiddenFlag(ObjectHideFlag_Mode,!bMessageEditing && m_bHideOtherEntities);
					break;
			}
		}
    }

	//hide/unhide cutscene objects as required
	Tools_Object_System::SetTypeHidden(TOT_Cutscene,ObjectHideFlag_User,HideCutsceneObjects);
	Tools_Object_System::SetTypeHidden(TOT_CutsceneActor,ObjectHideFlag_User,m_bHideActorObjects);
	Tools_Object_System::SetTypeHidden(TOT_CutsceneTrackPoint,ObjectHideFlag_User,m_bHideTrackObjects);
	Tools_Object_System::SetTypeHidden(TOT_CutsceneEvent,ObjectHideFlag_User,HideEventObjects);
	Tools_Object_System::SetTypeHidden(TOT_Entity_PhysicsObject,ObjectHideFlag_User,HideStaticObjects);
	Tools_Object_System::SetTypeHidden(TOT_Entity_Container,ObjectHideFlag_User,m_bHideContainerObjects);

}



void Tools_Mode_Cutscene::ImportCutscene()
{
	Tools_FileSystem_DialogInfo	xFileSelector;

	xFileSelector.DefaultExtension		= ".ExportedCutscene";
	xFileSelector.DefaultFilename		= "";
	xFileSelector.DefaultPath			= "";
	xFileSelector.FileFilter			= "Exported Cutscene Files\0*.ExportedCutscene;\0";
	xFileSelector.WindowTitle			= "Select cutscene entity file to load";
	xFileSelector.AllowMultipleSelect	= false;

	Asura_String xFilename = Tools_FileSystem::GetLoadFilename( xFileSelector );

	if(xFilename.IsNull()) return;
	
	//determine the name of the cutscene being imported
	Asura_String xCutsceneName;
	xFilename.SplitFilename(0,&xCutsceneName,0);

	//Find a name that can be used
	char szNewName[200];
	Asura_PrintfToBfr(szNewName,"%s",sizeof(szNewName),true,xCutsceneName.GetString());

	int iIndex = 1;
	while(Asura_Cutscene_System::GetCutsceneByHash(Asura_GetHashID(szNewName)))
	{
		Asura_PrintfToBfr(szNewName,"%s %d",sizeof(szNewName),true,xCutsceneName.GetString(),iIndex++);
	}

	ShutdownMode();

	//If a cutscene already exists that matches the one in the file ,then we need to give
	//it a temporary name , so it doesn't get in the way of the loaders
	char szTempName[200] = "";
	bool bUsingTempName = false;
	if(Asura_Cutscene_System::GetCutsceneByHash(Asura_GetHashID(xCutsceneName.GetString())))
	{
		iIndex = 1;
		do
		{
			Asura_PrintfToBfr(szTempName,"RenameTemp %d",sizeof(szTempName),true,iIndex++);
		}
		while(Asura_Cutscene_System::GetCutsceneByHash(Asura_GetHashID(szTempName)));

		if(!Asura_Cutscene_System::RenameCutscene(xCutsceneName.GetString(),szTempName))
		{
			Asura_Assert(0,"Failed to rename cutscene during import");
			InitialiseMode();
			return;
		}
		else
		{
			bUsingTempName = true;
		}
	}
	LoadAsuraFile(xFilename.GetString());

	if(bUsingTempName)
	{
		//rename the loaded cutscene to the name we found for it
		Asura_Cutscene_System::RenameCutscene(xCutsceneName.GetString(),szNewName);
		//rename the original cutscene back from the temp name
		Asura_Cutscene_System::RenameCutscene(szTempName,xCutsceneName.GetString());
	}

	//If the import worked , ask whether the cutscene should be placed at the camera
	Asura_Cutscene* pxImportedCutscene = Asura_Cutscene_System::GetCutsceneByHash(Asura_GetHashID(szNewName));
	if(pxImportedCutscene)
	{
		if(ShowYesNoOverlay("Would you like to move the imported cutscene to the current camera position?"))
		{
			pxImportedCutscene->CalculateRelativePosition(Asura_Maths::IdentityMatrix3x3,pxImportedCutscene->Position);
			pxImportedCutscene->UpdateRelativeToPosition(*Asura_Camera::GetOrientation(),*Asura_Camera::GetPosition());
			pxImportedCutscene->Position = *Asura_Camera::GetPosition();
		}
	}

	InitialiseMode();
}

void Tools_Mode_Cutscene::ReloadAnimations()
{
	if(CurrentCutscene) CurrentCutscene->Cutscene->ReloadAnimations();
	Tools_Timed_Message_System::AddMessage(0xffffff,"Reloaded animations for this cutscene");
}


void Tools_Mode_Cutscene::ActivateParticleGenerators(bool bActive)
{
	//activate/deactivate the client versions of all particle generators marked as StartsActive
	//actually , activate the lot of them! RAR 26/05/03
	//okay , just the ones with StartActive after all RAR 03/07/03

	for(Asura_Hashed_List_It<Asura_ClientEntity_PFX_Effect> xIt(Asura_ClientEntity_PFX_Effect::GetList()) ;!xIt.Done(); xIt.Next() )
	{
		Asura_ClientEntity_PFX_Effect* pxGenerator = xIt.GetCurrent();
		if(pxGenerator->IsDestroyed()) continue;

		//If recording an AVI , tell the generator to use its own RNG. RAR 22/05/2006
		pxGenerator->SetUseEffectSpecificRNG(Tools_CutsceneModeAVI::IsRecording());
		
		if(bActive && pxGenerator->StartsActive())
		{
			pxGenerator->Activate(true);
		}
		else
		{
			pxGenerator->Activate(false);
		}
	}
	
}

void Tools_Mode_Cutscene::SetTimerMode()
{
	int iMode = (s_eTimerMode + 1) %3;
	s_eTimerMode = (CutsceneTimerMode) iMode;

	UpdateCutscenePlaybackOverlay();
}

float Tools_Mode_Cutscene::ConvertSecondsToDisplayedTime(float fValue,int iMode)
{
	if(iMode == CUTSCENETIMER_25FPS || iMode == CUTSCENETIMER_30FPS)
	{
		float fFPS = (iMode == CUTSCENETIMER_25FPS) ? 25.0f : 30.0f;

		return (float) Asura_Maths::FloatToInt_Near(fValue*fFPS);
	}
	return fValue;
}

float Tools_Mode_Cutscene::ConvertDisplayedTimeToSeconds(float fValue,int iMode)
{
	if(iMode == CUTSCENETIMER_25FPS || iMode == CUTSCENETIMER_30FPS)
	{
		float fFPS = (iMode == CUTSCENETIMER_25FPS) ? 25.0f : 30.0f;

		return fValue/fFPS;
	}
	return fValue;
}

void Tools_Mode_Cutscene::DisablePFXClicked()
{
	Asura_PFX_System::DisablePFXSystem(s_pxThis->m_bDisablePFX);
	RefreshPlayback();	
}

void Tools_Mode_Cutscene::UpdateSoundTrack(float fTolerance)
{
	if(!CurrentCutscene) return;
	Asura_Cutscene* pxCutscene = CurrentCutscene->Cutscene;
	if(pxCutscene->SoundTrackHandle == ASURA_INVALID_HANDLE) return;

	//pause/play sound track depending on playback speed
	/*
	if(CutscenePlayback_Speed == 0.0f)
	{
		Asura_Sound_System::PauseStreamingSound(pxCutscene->SoundTrackHandle);
		return;
	}
	else
	{
		Asura_Sound_System::ResumeStreamingSound(pxCutscene->SoundTrackHandle);
	}
	*/
	if(CutscenePlayback_Speed == 0.0f) return;
	
	//determine the real time into the cutscene
	float fTimeScale = 1.0f;
	float fRealTime = 0.0f;
	float fCutsceneTime = pxCutscene->m_fCutsceneStartTime;
	
	for(int i=0;i<pxCutscene->NumberOfEvents;i++)
	{
		Asura_Cutscene_Event* pxEvent = pxCutscene->Events[i];

		if(pxEvent->m_fTriggerTime >= pxCutscene->Timer) break;

		if(pxEvent->m_iType == ACET_TimeScale)
		{
			//add up the real time to this event
			if(pxEvent->m_fTriggerTime > fCutsceneTime)
			{
				float fTimeDiff = pxEvent->m_fTriggerTime-fCutsceneTime;
				fRealTime += fTimeDiff/fTimeScale;
				fCutsceneTime = pxEvent->m_fTriggerTime;
			}
			
			//take note of the new time scale
			Asura_Cutscene_Event_TimeScale* pxTimeScaleEvent = (Asura_Cutscene_Event_TimeScale*) pxEvent;
			fTimeScale = pxTimeScaleEvent->m_fTimeScale;
			Asura_Assert(fTimeScale > 0.0f,"");
		}
	}
	//use up the remaining cutscene time
	if(pxCutscene->Timer > fCutsceneTime)
	{
		float fTimeDiff = pxCutscene->Timer-fCutsceneTime;
		fRealTime += fTimeDiff/fTimeScale;
	}

	#ifdef ASURA_USE_AUDIO_SYSTEM
	if(CutscenePlayback_TimesliderModified)
	{
		pxCutscene->m_fAudioStartTime = fRealTime;

		// If the user is dragging the timebar forwards in time, then recreate the sound with the new offset. For some reason, the
		// existing code reinitialises the system if the user drags it backwards in time, so there's no need to do it in this case.
		// For going backwards in time, I just set the m_fAudioStartTime variable, which is used when the sound gets recreated.

		if(CutscenePlayback_DeltaTime > 0.0f)
		{
			Asura_Audio_Voice* const pxOldVoice = Asura_Audio_System::GetVoice(pxCutscene->SoundTrackHandle);

			if(pxOldVoice)
			{
				float fTrackTime = pxOldVoice->GetPlayPosition();

				if(Asura_Maths::Abs(fTrackTime-fRealTime) > fTolerance)
				{
					Asura_Handle xSoundTrackHandle = Asura_Audio_System::CreateStreamingVoice(Asura_GetHashID(pxCutscene->SoundTrackName.GetString()), false, false, fRealTime);
					Asura_Assert(xSoundTrackHandle != ASURA_INVALID_HANDLE, "Failed to create streaming voice");

					if(xSoundTrackHandle != ASURA_INVALID_HANDLE)
					{
						Asura_Audio_Voice* const pxNewVoice = Asura_Audio_System::GetVoice(xSoundTrackHandle);

						Asura_Assert(pxNewVoice, "Failed to get voice from new streaming sound");

						if(pxNewVoice)
						{
							// Start the new voice in the new position
							pxNewVoice->Start();

							// Destroy the old voice
							Asura_Audio_System::DestroyVoice(pxCutscene->SoundTrackHandle);

							pxCutscene->SoundTrackHandle = xSoundTrackHandle;
						}
					}
				}
			}
		}
	}
	#else
	//get the time into the soundtrack
	float fTrackTime = Asura_Sound_System::GetStreamingSoundPosition(pxCutscene->SoundTrackHandle);

	if(Asura_Maths::Abs(fTrackTime-fRealTime) <= fTolerance)
	{
		//close enough
		return;
	}

	//move the position in the sound track
	Asura_Sound_System::SetStreamingSoundPosition(pxCutscene->SoundTrackHandle,fRealTime);
	#endif
}

Asura_Cutscene* Tools_Mode_Cutscene::GetCurrentCutscene()
{
	return CurrentCutscene ? CurrentCutscene->Cutscene : NULL;
}

void Tools_Mode_Cutscene::Verify(bool bAutomatic)
{
	if(bAutomatic && !Tools_MessageLog::ShouldShowForCutscene())
	{
		return;
	}

	//Clear out existing messages
	Tools_MessageLog::DeleteMessages(Tools_MessageLog::CUTSCENE,0);
	//Get current position to see if any errors were logged
	u_int uInitialPos = Tools_MessageLog::GetNextID();

	int iNumCutscenes = Asura_Cutscene_System::GetNumberOfCutscenes();
	for(int i=0; i<iNumCutscenes; i++)
	{
		Asura_Cutscene* pxCutscene = Asura_Cutscene_System::GetCutsceneByIndex(i);

		//Check for anim/skins files that aren't where they are supposed to be
		for(int j=0;j<2;j++)
		{
			Asura_NameList* pxNames = j ? &pxCutscene->SkinFileNames : &pxCutscene->AnimationFileNames;
			for(int k=0;k<pxNames->GetNumEntries();k++)
			{
				const Asura_String& xName = pxNames->GetName(k);
				if(xName.IsBlank()) continue;

				if(!Tools_FileSystem::FileExists_CheckNetwork(xName.GetString()))
				{
					Tools_MessageLog::AddMessage(Tools_MessageLog::CUTSCENE,"",new Tools_MessageLogAction_Cutscene(pxCutscene),
												 "Can't find %s %s for %s",
												 j ? "skin" : "anim",
												 xName.GetString(),
												 pxCutscene->GetName().GetString());
				}
			}
		}

		//Check for cutscenes that have had their message block placed in the wrong file
		if(pxCutscene->m_usEndCutsceneMessageBlock != ASURA_STATICMESSAGE_EMPTYBLOCK)
		{
			int iIndex;
			Asura_StaticMessageSetIndex eSet;
			Asura_StaticMessage_System::ConvertIDToIndex(pxCutscene->m_usEndCutsceneMessageBlock, &iIndex, &eSet);

			if(eSet != ASURA_STATICMESSAGE_CUTSCENESET)
			{
				Tools_MessageLog::AddMessage(Tools_MessageLog::CUTSCENE,"",new Tools_MessageLogAction_Cutscene(pxCutscene),
											 "End of cutscene messages for %s are in the wrong file",
											 pxCutscene->GetName().GetString());
			}
		}
	}


	if(Tools_MessageLog::AreThereAnyMessages(Tools_MessageLog::CUTSCENE, 0, uInitialPos))
	{
		//Found some errors ,so show message log
		Tools_MessageLog::ShowOverlay(Tools_MessageLog::CUTSCENE,0,uInitialPos);
	}
	else if(!bAutomatic)
	{
		ShowMessageOverlay("No cutscene system errors found.");
	}

}

void Tools_Mode_Cutscene::UpdateTimelineBar()
{
	Asura_Overlay* pxOverlay = Asura_Overlay_System::GetOverlay(TOOLS_OVERLAY_CUTSCENE_PLAYBACKSLIDER);
	if(!pxOverlay) return;

	Asura_Dialog* pxDialog = pxOverlay->GetDialog(Asura_GetHashID("TimelineBar"));
	if(!pxDialog) return;

	((Tools_CutsceneTimeline_Dialog*)pxDialog)->UpdateFromCutsceneMode(CutscenePlaybackOverlayPosition.y);
}

void Tools_Mode_Cutscene::SendStaticMessage(const Asura_StaticMessage* pxStaticMessage, Asura_Guid uFrom)
{
	#ifdef ASURA_USE_DIALOGUE_SYSTEM
	if(pxStaticMessage->m_usMsgID == ASURA_MSG_DIALOGUE_PLAYLINEONENTITY || 
		pxStaticMessage->m_usMsgID == ASURA_MSG_DIALOGUE_PLAYLINE ||
		pxStaticMessage->m_usMsgID == ASURA_MSG_DIALOGUE_CANCELSUBTITLE)
	{
		//just send this message normally , since it shouldn't cause anything nasty to happen.
		//RAR 18/12/2006
		Asura_Message* pxMessage = Asura_Network::CreateMessage(pxStaticMessage->m_usMsgID, pxStaticMessage->m_usDataSize);
		if(pxMessage)
		{
			pxMessage->To = pxStaticMessage->m_uSendTo;
			pxMessage->From = uFrom;
			if(pxStaticMessage->m_usDataSize)
			{
				Asura_Mem::Copy(pxMessage->GetData(), pxStaticMessage->m_pData, pxStaticMessage->m_usDataSize);
			}
	
			Asura_Network::Send(pxMessage);
		}

		return;
	}
	#endif

	Asura_Entity_Client* pxEntity = Asura_Entity_Client::FindEntity(pxStaticMessage->m_uSendTo);
	if(!pxEntity) return;
	switch(pxEntity->GetClassification())
	{

		//Activate/deactivate the client directly
		case AsuraEntityClass_PFX_Effect:
		{
			Asura_ClientEntity_PFX_Effect* pxGenerator = (Asura_ClientEntity_PFX_Effect*)pxEntity;

			if(pxStaticMessage->m_usMsgID == ASURA_MSG_ACTIVATE)
			{
				pxGenerator->Activate(true);
			}
			else if(pxStaticMessage->m_usMsgID == ASURA_MSG_DEACTIVATE)
			{
				pxGenerator->Activate(false);
			}
			break;
		}

		case AsuraEntityClass_SoundController :
		{
			Asura_ClientEntity_SoundController* pxSoundController = (Asura_ClientEntity_SoundController*)pxEntity;

			//fiddle with the client entity directly
			if(pxStaticMessage->m_usMsgID == ASURA_MSG_ACTIVATE)
			{
				pxSoundController->Activate(true);
			}
			else if(pxStaticMessage->m_usMsgID == ASURA_MSG_DEACTIVATE)
			{
				pxSoundController->Activate(false);
			}
			else if(pxStaticMessage->m_usMsgID == ASURA_MSG_TRIGGER)
			{
				pxSoundController->Trigger();
			}

			break;
		}

		default :
			break;
	}
}


struct CutsceneReportResourceInfo
{
	Asura_String m_xName;
	Asura_String m_xFilename;
	Asura_String m_xCutscene;
	Asura_String m_xActor;
	bool m_bSkin;
};

static int CutsceneReportSort(const void* p1,const void* p2)
{
	const CutsceneReportResourceInfo* pxEntry1 = *((const CutsceneReportResourceInfo**)p1);
	const CutsceneReportResourceInfo* pxEntry2 = *((const CutsceneReportResourceInfo**)p2);

	int iCompare = pxEntry1->m_xName.Compare(pxEntry2->m_xName);
	if(iCompare) return iCompare;

	iCompare = pxEntry1->m_xFilename.Compare(pxEntry2->m_xFilename);
	if(iCompare) return iCompare;

	iCompare = pxEntry1->m_xCutscene.Compare(pxEntry2->m_xCutscene);
	if(iCompare) return iCompare;

	iCompare = pxEntry1->m_xActor.Compare(pxEntry2->m_xActor);
	return iCompare;
}


static CutsceneReportResourceInfo** g_ppxCutsceneReportArray;
static int g_iCutsceneReportArraySize;
static int g_iNumCutsceneReportEntries;

void Tools_Mode_Cutscene::AddCutsceneReportEntry(const char* szName, const char* szFilename, const char* szCutscene, const char* szActor, bool bSkin)
{
	//check for an existing entry
	for(int i=0;i< g_iNumCutsceneReportEntries; i++)
	{
		const CutsceneReportResourceInfo* pxEntry = g_ppxCutsceneReportArray[i];
		if(!stricmp(szFilename, pxEntry->m_xFilename.GetString()) &&
			!stricmp(szCutscene, pxEntry->m_xCutscene.GetString()) &&
			!stricmp(szActor, pxEntry->m_xActor.GetString()) &&
			bSkin == pxEntry->m_bSkin)
		{
			return;
		}
	}

	if(g_iNumCutsceneReportEntries == g_iCutsceneReportArraySize)
	{
		//enlarge the array
		g_iCutsceneReportArraySize += 16;
		CutsceneReportResourceInfo** ppxNewArray = new CutsceneReportResourceInfo*[g_iCutsceneReportArraySize];
		if(g_iNumCutsceneReportEntries)
		{
			Asura_Mem::Copy(ppxNewArray, g_ppxCutsceneReportArray, g_iNumCutsceneReportEntries*sizeof(CutsceneReportResourceInfo*));
		}
		delete [] g_ppxCutsceneReportArray;
		g_ppxCutsceneReportArray = ppxNewArray;
	}

	//add a new entry for this combination
	CutsceneReportResourceInfo* pxNewEntry = new CutsceneReportResourceInfo;
	pxNewEntry->m_xName = szName;
	pxNewEntry->m_xFilename = szFilename;
	pxNewEntry->m_xCutscene = szCutscene;
	pxNewEntry->m_xActor = szActor;
	pxNewEntry->m_bSkin = bSkin;
	g_ppxCutsceneReportArray[g_iNumCutsceneReportEntries] = pxNewEntry;
	g_iNumCutsceneReportEntries++;
}

void Tools_Mode_Cutscene::CreateCutsceneReport()
{
	//get a filename to save as

	#if 0
	//ask for a filename
	Tools_FileSystem_DialogInfo xInfo;

	xInfo.DefaultExtension = "csv";

	xInfo.DefaultFilename = "CutsceneReport.csv";
	xInfo.DefaultPath = ".";
	xInfo.FileFilter = "Text Files\0*.csv\0";
	xInfo.WindowTitle = "Save cutscene report file as :";

	Asura_String xLogFilename = Tools_FileSystem::GetSaveFilename(xInfo);
	if(xLogFilename.IsBlank()) return;
	#else
	//Build the filename for the report by appending _CutsceneReport.csv onto the
	//name of the asura file
	Asura_String	xEnvPath;
	Asura_String	xEnvFilename;
	Asura_String 	xFullEnvironmentFilename = Tools_SaveAsura::IsEditingMission() ? Tools_SaveAsura::GetMissionFilenameWithoutExtension() : Tools_SaveAsura::GetMainFilename();

	xFullEnvironmentFilename.SplitFilename(&xEnvPath, &xEnvFilename, NULL);
	xEnvPath += "\\Logfiles\\";
	Asura_String xLogFilename = xEnvPath;
	xLogFilename +=  xEnvFilename;
	xLogFilename +=  "_CutsceneReport.csv";
	#endif



	//make sure the file can be created
	Tools_FileSystem::EnsureDirectoryExists(xLogFilename.GetString());
	FILE* pxFile = fopen(xLogFilename.GetString(), "wt");
	if(!pxFile)
	{
		Tools_Timed_Message_System::AddMessage(0xff0000,"Failed to open %s for writing",xLogFilename.GetString());
		return;
	}

	

	for(int iCutscene=0; iCutscene < Asura_Cutscene_System::GetNumberOfCutscenes(); iCutscene++)
	{
		Asura_Cutscene* pxCutscene = Asura_Cutscene_System::GetCutsceneByIndex(iCutscene);
		const char* szCutscene = pxCutscene->GetName().GetString();

		//for each entry in the skin and animation name lists , check all the actors and events to
		//determine what actor uses them

		for(int iSkinIndex=0; iSkinIndex< pxCutscene->SkinFileNames.GetNumEntries(); iSkinIndex++)
		{
			const Asura_String& xFilename = pxCutscene->SkinFileNames.GetName(iSkinIndex);
			if(xFilename.IsBlank()) continue;

			const Asura_String& xName = pxCutscene->SkinNames.GetName(iSkinIndex);
			if(xName.IsBlank()) continue;


			//check each actor
			bool bFound = false;
			for(int i=0; i<pxCutscene->NumberOfActors; i++)
			{
				if(pxCutscene->Actors[i]->UsesSkin(iSkinIndex))
				{
					AddCutsceneReportEntry(xName.GetString(), xFilename.GetString(), szCutscene, pxCutscene->Actors[i]->ActorName.GetString(), true);
					bFound = true;
					break;
				}
			}
			if(!bFound)
			{
				AddCutsceneReportEntry(xName.GetString(), xFilename.GetString(), szCutscene, "*Unknown*", true);
			}
		}

		for(int iAnimIndex=0; iAnimIndex<pxCutscene->AnimationFileNames.GetNumEntries(); iAnimIndex++)
		{
			const Asura_String& xFilename = pxCutscene->AnimationFileNames.GetName(iAnimIndex);
			if(xFilename.IsBlank()) continue;

			const Asura_String& xName = pxCutscene->AnimationNames.GetName(iAnimIndex);
			if(xName.IsBlank()) continue;


			//check each actor
			bool bFound = false;
			for(int i=0; i<pxCutscene->NumberOfActors; i++)
			{
				if(pxCutscene->Actors[i]->UsesAnim(iAnimIndex))
				{
					AddCutsceneReportEntry(xName.GetString(), xFilename.GetString(), szCutscene, pxCutscene->Actors[i]->ActorName.GetString(), false);
					bFound = true;
					break;
				}
			}
			if(bFound) continue;

			//check each event
			for(int i=0; i<pxCutscene->NumberOfEvents; i++)
			{
				if(pxCutscene->Events[i]->UsesAnim(iAnimIndex))
				{
					const char* szActor = pxCutscene->Events[i]->m_xTargetActorName.GetString();
					if(szActor == 0 || *szActor == 0) szActor = "None";

					AddCutsceneReportEntry(xName.GetString(), xFilename.GetString(), szCutscene, szActor, false);
					
				}
			}
			if(bFound) continue;

			//failed to find anything for this entry
			AddCutsceneReportEntry(xName.GetString(), xFilename.GetString(), szCutscene, "*Unknown*", false);
			
		}
	}

	//sort the entries
	qsort(g_ppxCutsceneReportArray,g_iNumCutsceneReportEntries,sizeof(CutsceneReportResourceInfo*),CutsceneReportSort);

	//write out the .csv file
	
	//write the headings
	fprintf(pxFile,"Name,Type,File,Cutscene,Actor\n");

	//write out each entry
	for(int i=0; i<g_iNumCutsceneReportEntries; i++)
	{
		const CutsceneReportResourceInfo* pxEntry = g_ppxCutsceneReportArray[i];

		const char* szType = pxEntry->m_bSkin ? "Skin" : "Anim";
		fprintf(pxFile, "%s,%s,%s,%s,%s\n", pxEntry->m_xName.GetString(), szType, pxEntry->m_xFilename.GetString(), pxEntry->m_xCutscene.GetString(), pxEntry->m_xActor.GetString());
	}

	fclose(pxFile);

	Tools_Timed_Message_System::AddMessage(0xffffff,"%s written",xLogFilename.GetString());
	

	for(int i=0; i<g_iNumCutsceneReportEntries; i++)
	{
		delete g_ppxCutsceneReportArray[i];
	}
	delete [] g_ppxCutsceneReportArray;
	g_ppxCutsceneReportArray = 0;
	g_iCutsceneReportArraySize = 0;
	g_iNumCutsceneReportEntries = 0;
}


void Tools_Mode_Cutscene::ImportCutsceneFromTextFile()
{

	//Ask for the file to be loaded
	Tools_FileSystem_DialogInfo	xFileSelector;

	xFileSelector.DefaultExtension		= ".csl";
	xFileSelector.DefaultFilename		= "";
	xFileSelector.DefaultPath			= "";
	xFileSelector.FileFilter			= "Cutscene Text Files\0*.csl;\0";
	xFileSelector.WindowTitle			= "Import cutscene from which text file?";
	xFileSelector.AllowMultipleSelect	= false;

	Asura_String xFilename = Tools_FileSystem::GetLoadFilename( xFileSelector );

	if(xFilename.IsNull()) return;

	Tools_IniFile xIni(xFilename.GetString(), false);
	if(!xIni.LoadIniFile())
	{
		ShowMessageOverlay("Failed to load %s",xFilename.GetString());
	}

	//get the current position of the message log
	u_int uNextMessageLogID = Tools_MessageLog::GetNextID();

	//get the details of the cutscene to import to
	if(!xIni.GetIniTextSection("CUTSCENE"))
	{
		ShowMessageOverlay("Failed to find [CUTSCENE] section in %s", xFilename.GetString());
		return;
	}

	Asura_String xCutsceneName;
	if(!xIni.GetStringInCurrentSection("Name", xCutsceneName, true))
	{
		ShowMessageOverlay("Failed to get cutscene name");
		return;
	}


	//does the cutscene already exist?
	Asura_Cutscene* pxCutscene = Asura_Cutscene_System::GetCutsceneByHash(xCutsceneName.GetHash());
	bool bNewCutscene = false;
	if(pxCutscene)
	{
		//See whether stuff should be added to the existing cutscene
		bool bContinue = ShowYesNoOverlay("Cutscene '%s' already exists.\nContinue with importing actors and events into this cutscene?",xCutsceneName.GetString());
		if(!bContinue) return;
	}
	else
	{
		//create a new one
		pxCutscene = AllocateCutscene(xCutsceneName, ACT_StandardCutscene);
		Asura_Assert(pxCutscene, "");

		Asura_Cutscene_System::AddCutscene(pxCutscene);

		bNewCutscene = true;
	}
	
	//read in the rest of the details for the cutscene
	xIni.GetFloatInCurrentSection("Start",pxCutscene->m_fCutsceneStartTime);
	xIni.GetFloatInCurrentSection("End",pxCutscene->CutsceneEndTime);
	bool bWideScreen;
	if(xIni.GetBoolInCurrentSection("Widescreen",bWideScreen))
	{
		if(bWideScreen)
			pxCutscene->m_iCutsceneFlags |= AsuraCutsceneFlag_Widescreen;
		else
			pxCutscene->m_iCutsceneFlags &=~ AsuraCutsceneFlag_Widescreen;
	}

	Asura_String xPosText;
	if(xIni.GetStringInCurrentSection("Position", xPosText, true))
	{
		float fX,fY,fZ;
		if(sscanf(xPosText.GetString(),"%f,%f,%f",&fX,&fY,&fZ) == 3)
		{
			pxCutscene->Position.x = fX;
			pxCutscene->Position.y = -fZ;
			pxCutscene->Position.z = fY;
		}
	}


	//parse the rest of the file
	int iNumSection = xIni.GetNumberOfSections();
	for(int i=0; i<iNumSection; i++)
	{
		xIni.GetIniTextSectionByIndex(i);
		const char* szSectionName = xIni.GetCurrentSectionName();

		//determine the type of thing being created
		if(!stricmp(szSectionName, "ACTOR"))
		{
			ImportFromTextFile_Actor(xIni, pxCutscene);
		}
		else if(!stricmp(szSectionName, "CAMERA"))
		{
			ImportFromTextFile_Camera(xIni, pxCutscene);
		}
		else if(!stricmp(szSectionName, "ATTACHMENT"))
		{
			ImportFromTextFile_Attachment(xIni, pxCutscene);
		}
		else if(!stricmp(szSectionName, "EVENT"))
		{
			ImportFromTextFile_Event(xIni, pxCutscene);
		}
		else if(!stricmp(szSectionName, "CUTSCENE"))
		{
		}
		else
		{
			Tools_MessageLog::AddMessage(Tools_MessageLog::CUTSCENE, "Import", "Unrecognised section name : [%s]", szSectionName);
		}
	}

	//show the messag log if anything has been posted
	if(uNextMessageLogID != Tools_MessageLog::GetNextID())
	{
		Tools_MessageLog::ShowOverlay(Tools_MessageLog::CUTSCENE, 0, uNextMessageLogID);
	}
	else
	{
		Tools_Timed_Message_System::AddMessage(0xffffff, "Imported cutscene from %s",xFilename.GetString());
	}

	if(bNewCutscene)
	{
		Tools_Cutscene_Object* pxCutsceneObject = CreateCutsceneObject(pxCutscene);
		EditCutscene(pxCutsceneObject);
	}
	else
	{
		Tools_Cutscene_Object* pxCutsceneObject = GetCutsceneObject(xCutsceneName.GetString());
		Asura_Assert(pxCutsceneObject, "");
		EditCutscene(0);
		EditCutscene(pxCutsceneObject);

	}

}

void Tools_Mode_Cutscene::ImportFromTextFile_Actor(Tools_IniFile& xIni, Asura_Cutscene* pxCutscene)
{
	Asura_String xActorName;
	if(!xIni.GetStringInCurrentSection("Name",xActorName,true))
	{
		Tools_MessageLog::AddMessage(Tools_MessageLog::CUTSCENE, "Import", "Failed to get actor name");
		return;
	}

	Asura_Cutscene_Actor* pxBaseActor = Asura_Cutscene_Actor::AllocateActor(ACAT_Hierarchy, false, pxCutscene);
	Asura_Assert(pxBaseActor && pxBaseActor->IsHierarchyActor(), "");
	Asura_Cutscene_Actor_Hierarchy* pxActor = (Asura_Cutscene_Actor_Hierarchy*) pxBaseActor;

	pxActor->ActorName = xActorName;


	//skin
	Asura_String xSkinFileName;
	if(xIni.GetStringInCurrentSection("Skin", xSkinFileName, true))
	{
		Asura_String xSkinName;
		xSkinFileName.SplitFilename(0,&xSkinName,0);

		const char* szRelativeSkinFileName = GetRelativeFilename(xSkinFileName);

		pxActor->m_aiSkinID[0] = pxCutscene->SkinNames.AddName(xSkinName);
		pxCutscene->SkinFileNames.AddName(szRelativeSkinFileName);

		pxActor->LoadFiles();
		Asura_Texture_Management::LoadTextures();

		//see if skin was loaded successfully
		//if(!Asura_Animation_System::GetSkin(hierarchyActor->SkinName))
	    if(!Asura_Animation_System::GetSkin(xSkinName.GetString()))
		{
			Tools_MessageLog::AddMessage(Tools_MessageLog::CUTSCENE, "Import", "Failed to find load %s for actor %s",xSkinFileName.GetString(),xActorName.GetString());
		}
	}

	//Position/Orientation
	Asura_Vector_3 xPos;
	Asura_Matrix_3x3 xMat = Asura_Maths::IdentityMatrix3x3;
	if(Get3dsMaxPositionFromTextFile(xIni, "Position", xPos))
	{
		Get3dsMaxOrientationFromTextFile(xIni, "Rotation", xMat);

		//Setup a default track if the actor hasn't got one yet.
		//Leave alone for now if there are tracks already
		if(!pxActor->NumberOfTracks)
		{
			Asura_Cutscene_Track* pxTrack = new Asura_Cutscene_Track;
			pxTrack->SetPoints(1,&xPos);
			pxTrack->SetOrientationAtPoint(0,xMat);
			pxTrack->TrackName = pxActor->ActorName;

			Asura_ResourcePushType(ASURA_RESOURCE_CUTSCENE);
			{
				pxActor->NumberOfTracks = 1;
				Asura_ResourceAllocateArray(pxActor->Tracks, 1);
				pxActor->Tracks[0] = pxTrack;
			}
			Asura_ResourcePopType();

			pxActor->Position = xPos;
			pxActor->Orientation = xMat;
		}
	}

	pxCutscene->AddActor(pxActor);	
}

void Tools_Mode_Cutscene::ImportFromTextFile_Camera(Tools_IniFile& xIni, Asura_Cutscene* pxCutscene)
{
	Asura_String xActorName;
	if(!xIni.GetStringInCurrentSection("Name",xActorName,true))
	{
		Tools_MessageLog::AddMessage(Tools_MessageLog::CUTSCENE, "Import", "Failed to get camera name");
		return;
	}

	Asura_Cutscene_Actor* pxBaseActor = Asura_Cutscene_Actor::AllocateActor(ACAT_Camera, false, pxCutscene);
	Asura_Assert(pxBaseActor && pxBaseActor->IsCamera(), "");
	Asura_Cutscene_Actor_Camera* pxActor = (Asura_Cutscene_Actor_Camera*) pxBaseActor;

	pxActor->ActorName = xActorName;

	//Position/Orientation
	Asura_Vector_3 xPos;
	Asura_Matrix_3x3 xMat = Asura_Maths::IdentityMatrix3x3;
	if(Get3dsMaxPositionFromTextFile(xIni, "Position", xPos))
	{
		Get3dsMaxOrientationFromTextFile(xIni, "Rotation", xMat);

		//Setup a default track if the actor hasn't got one yet.
		//Leave alone for now if there are tracks already
		if(!pxActor->NumberOfTracks)
		{
			Asura_Cutscene_Track* pxTrack = new Asura_Cutscene_Track;
			pxTrack->SetPoints(1,&xPos);
			pxTrack->SetOrientationAtPoint(0,xMat);
			pxTrack->TrackName = pxActor->ActorName;

			Asura_ResourcePushType(ASURA_RESOURCE_CUTSCENE);
			{
				pxActor->NumberOfTracks = 1;
				Asura_ResourceAllocateArray(pxActor->Tracks, 1);
				pxActor->Tracks[0] = pxTrack;
			}
			Asura_ResourcePopType();

			pxActor->Position = xPos;
			pxActor->Orientation = xMat;
		}
	}

	//FOV
	float fFOV;
	if(xIni.GetFloatInCurrentSection("FOV", fFOV))
	{
		pxActor->SetInitialFOV(Asura_Maths::DegToRad(fFOV)); 
	}


	pxCutscene->AddActor(pxActor);	
	pxCutscene->InvalidateNumberOfCameras();
}

void Tools_Mode_Cutscene::ImportFromTextFile_Attachment(Tools_IniFile& xIni, Asura_Cutscene* pxCutscene)
{
	//get the parent actor's name
	Asura_String xParentName;
	if(!xIni.GetStringInCurrentSection("Parent", xParentName, true))
	{
		Tools_MessageLog::AddMessage(Tools_MessageLog::CUTSCENE, "Import", "Failed to get parent name for attachment");
		return;
	}

	//find it , and make sure it is a hierarchy actor
	Asura_Cutscene_Actor* pxActor = pxCutscene->GetActorByName(xParentName.GetString());
	if(!pxActor)
	{
		Tools_MessageLog::AddMessage(Tools_MessageLog::CUTSCENE, "Import", "Failed to find actor '%s' for attachment", xParentName.GetString());
		return;
	}
	if(!pxActor->IsHierarchyActor())
	{
		Tools_MessageLog::AddMessage(Tools_MessageLog::CUTSCENE, "Import", "Parent actor '%s' for attachment isn't a hierarchy actor", xParentName.GetString());
		return;
	}
	Asura_Cutscene_Actor_Hierarchy* pxParentActor = (Asura_Cutscene_Actor_Hierarchy*) pxActor;

	//Get the name of the actor being attached , and make sure that actor exists
	Asura_String xActorName;
	if(!xIni.GetStringInCurrentSection("Actor", xActorName, true))
	{
		Tools_MessageLog::AddMessage(Tools_MessageLog::CUTSCENE, "Import", "Failed to get actor name for attachment");
		return;
	}
	Asura_Cutscene_Actor* pxAttachedActor = pxCutscene->GetActorByName(xActorName.GetString());
	if(!pxAttachedActor)
	{
		Tools_MessageLog::AddMessage(Tools_MessageLog::CUTSCENE, "Import", "Failed to find attached actor '%s'",xActorName.GetString());
		return;
	}

	//get the marker point to use
	Asura_String xMarkerName;
	if(!xIni.GetStringInCurrentSection("Marker", xMarkerName, true))
	{
		Tools_MessageLog::AddMessage(Tools_MessageLog::CUTSCENE, "Import", "Failed to get marker point name for attachment");
		return;
	}

	//can now go ahead and create the attachment
	Asura_Cutscene_Attachment* pxBaseAttachment = Asura_Cutscene_Attachment::AllocateAttachment(pxParentActor, AsuraCutsceneAttachment_Actor);
	Asura_Assert(pxBaseAttachment, "");
	Asura_Cutscene_Attachment_Actor* pxAttachment = (Asura_Cutscene_Attachment_Actor*) pxBaseAttachment;

	Asura_ResourcePushType(ASURA_RESOURCE_CUTSCENE);
	{
		//enlarge the array of attachments
		pxParentActor->m_iNumberOfAttachments++;
		Asura_Cutscene_Attachment** ppxNewAttachments;
		Asura_ResourceAllocateArray(ppxNewAttachments, pxParentActor->m_iNumberOfAttachments);
		for(int i=0;i<pxParentActor->m_iNumberOfAttachments-1;i++)
		{
			ppxNewAttachments[i] = pxParentActor->m_ppxAttachments[i];
		}

		//add on the new attachment
		ppxNewAttachments[pxParentActor->m_iNumberOfAttachments-1] = pxAttachment;

		//replace the old array
		Asura_ResourceDeallocateArray(pxParentActor->m_ppxAttachments);
		pxParentActor->m_ppxAttachments = ppxNewAttachments;
	}
	Asura_ResourcePopType();

	//fill in the attachment details
	pxAttachment->SetMarkerName(xMarkerName );	
	pxAttachment->SetActorName(xParentName.GetString());

	Asura_String xName;
	if(xIni.GetStringInCurrentSection("Name", xName, true))
	{
		pxAttachment->SetName(xName.GetString());
	}

}

void Tools_Mode_Cutscene::ImportFromTextFile_Event(Tools_IniFile& xIni, Asura_Cutscene* pxCutscene)
{
	//determine the type of event to create
	Asura_String xType;
	if(!xIni.GetStringInCurrentSection("Type", xType, true))
	{
		Tools_MessageLog::AddMessage(Tools_MessageLog::CUTSCENE, "Import", "Failed to get event type");
		return;
	}

	int iEventType;
	if(!xType.Compare("Animation Change"))
	{
		iEventType = ACET_AnimationChange;
	}
	else if(!xType.Compare("Change Camera"))
	{
		iEventType = ACET_ChangeCamera;
	}
	else if(!xType.Compare("Field Of View"))
	{
		iEventType = ACET_FieldOfView;
	}
	else
	{
		Tools_MessageLog::AddMessage(Tools_MessageLog::CUTSCENE, "Import", "Unrecognised event type '%s'",xType.GetString());
		return;
	}

	//allocate the new event
	Asura_Cutscene_Event* pxEvent = Asura_Cutscene_Event::AllocateEvent(iEventType, pxCutscene);
	Asura_ResourcePushType(ASURA_RESOURCE_CUTSCENE);
	{
		//enlarge the array of pxEvents
		pxCutscene->NumberOfEvents++;
		Asura_Cutscene_Event** ppxNewEvents;
		Asura_ResourceAllocateArray(ppxNewEvents, pxCutscene->NumberOfEvents);
		for(int i=0;i<pxCutscene->NumberOfEvents-1;i++)
		{
			ppxNewEvents[i] = pxCutscene->Events[i];
		}

		//add on the new pxEvent
		ppxNewEvents[pxCutscene->NumberOfEvents-1] = pxEvent;

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

	xIni.GetFloatInCurrentSection("Time", pxEvent->m_fTriggerTime);
	xIni.GetStringInCurrentSection("Name", pxEvent->m_xName,true);

	//get the target actor
	Asura_String xActorName;
	if(xIni.GetStringInCurrentSection("Target",xActorName,true))
	{
		Asura_Cutscene_Actor* pxTarget = pxCutscene->GetActorByName(xActorName.GetString());
		if(pxTarget)
		{
			pxEvent->m_pxTargetActor = pxTarget;
			pxEvent->m_xTargetActorName = xActorName;
		}
		else
		{
			Tools_MessageLog::AddMessage(Tools_MessageLog::CUTSCENE, "Import", "Failed to find target actor '%s' for event",xActorName.GetString());
		}		
	}
	else
	{
		Tools_MessageLog::AddMessage(Tools_MessageLog::CUTSCENE, "Import", "Failed to get target actor for event");
	}


	//parse the rest of the event details
	switch(iEventType)
	{
		case ACET_AnimationChange :
			ImportFromTextFile_Event_AnimationChange(xIni, pxCutscene, pxEvent);
			break;

		case ACET_ChangeCamera :
			//nothing more to do for this event
			break;

		case ACET_FieldOfView :
			ImportFromTextFile_Event_FieldOfView(xIni, pxCutscene, pxEvent);
			break;

		default :
			Asura_Assert(0,"Unhandled event type");
			break;
	}

}

void Tools_Mode_Cutscene::ImportFromTextFile_Event_AnimationChange(Tools_IniFile& xIni, Asura_Cutscene* pxCutscene, Asura_Cutscene_Event* pxBaseEvent)
{
	Asura_Cutscene_Event_AnimationChange* pxEvent = (Asura_Cutscene_Event_AnimationChange*) pxBaseEvent;

	//get the name of the animation to use
	Asura_String xAnimFileName;
	if(xIni.GetStringInCurrentSection("Anim",xAnimFileName,true))
	{
		Asura_String xAnimName;

		xAnimFileName.SplitFilename(0,&xAnimName,0);
		const char* szRelativeAnimFileName = GetRelativeFilename(xAnimFileName);


		pxEvent->m_iAnimationIndex = pxCutscene->AnimationNames.AddName(xAnimName);
		pxEvent->m_iAnimationFlags &=~ AnimationChangeFlag_Loop;
		pxCutscene->AnimationFileNames.AddName(szRelativeAnimFileName);
	}
}


void Tools_Mode_Cutscene::ImportFromTextFile_Event_FieldOfView(Tools_IniFile& xIni, Asura_Cutscene* pxCutscene, Asura_Cutscene_Event* pxBaseEvent)
{
	Asura_Cutscene_Event_FieldOfView* pxEvent = (Asura_Cutscene_Event_FieldOfView*) pxBaseEvent;

	if(!xIni.GetFloatInCurrentSection("Duration",pxEvent->m_fDuration))
	{
		Tools_MessageLog::AddMessage(Tools_MessageLog::CUTSCENE, "Import", "Failed to get duration for FOV event");
	}

	float fAngle;
	if(!xIni.GetFloatInCurrentSection("FOV",fAngle))
	{
		Tools_MessageLog::AddMessage(Tools_MessageLog::CUTSCENE, "Import", "Failed to get FOV for FOV event");
	}
	else
	{
		pxEvent->m_fFinalFOV = Asura_Maths::DegToRad(fAngle);
	}

}

bool Tools_Mode_Cutscene::Get3dsMaxPositionFromTextFile(Tools_IniFile& xIni, const char* szID, Asura_Vector_3& xOutputPos)
{
	Asura_String xText;
	if(!xIni.GetStringInCurrentSection(szID, xText)) return false;

	float fX,fY,fZ;
	if(sscanf(xText.GetString(),"%f,%f,%f",&fX,&fY,&fZ) != 3) return false;

	//convert from Max axes to asura
	xOutputPos.x = fX;
	xOutputPos.y = -fZ;
	xOutputPos.z = fY;
	return true;
}

bool Tools_Mode_Cutscene::Get3dsMaxOrientationFromTextFile(Tools_IniFile& xIni, const char* szID, Asura_Matrix_3x3& xOutputMat)
{
	Asura_String xText;
	if(!xIni.GetStringInCurrentSection(szID, xText)) return false;

	float fX,fY,fZ;
	if(sscanf(xText.GetString(),"%f,%f,%f",&fX,&fY,&fZ) != 3) return false;

	//convert from Max axes to asura , and convert to radians
	float fEulerX = Asura_Maths::DegToRad(fX);
	float fEulerY = Asura_Maths::DegToRad(-fZ);
	float fEulerZ = Asura_Maths::DegToRad(fY);

	//3dsMax does the rotations in the order X,Y,Z , which is equivalent to X,Z,Y for asura axes
	//So , can't use our standard euler to matrix function for this.

	//get the 3 rotations as quaterions , and combine them
	Asura_Quat xQuatX,xQuatY,xQuatZ;
	xQuatX.FromRotation(fEulerX,Asura_Vector_3(1.0f,0.0f,0.0f));
	xQuatY.FromRotation(fEulerY,Asura_Vector_3(0.0f,1.0f,0.0f));
	xQuatZ.FromRotation(fEulerZ,Asura_Vector_3(0.0f,0.0f,1.0f));

	Asura_Quat xFinalQuat = xQuatX*xQuatZ*xQuatY;
	xFinalQuat.ToMatrix(xOutputMat);

	return true;	
}

const char* Tools_Mode_Cutscene::GetRelativeFilename(Asura_String& xFilename)
{
	//make sure all the slashes are in the expected direction
	xFilename.TidyFilename();
	
	const char* szDir = "\\Asura_Files\\";
	const int iDirLength = strlen(szDir);
	
	//Check each part of the path for 'Asura_Files'
	const char* pcSlash = strchr(xFilename.GetString(),'\\');
	while(pcSlash)
	{
		if(!strnicmp(pcSlash,szDir,iDirLength))
		{
			return pcSlash + iDirLength;
		}
		pcSlash = strchr(pcSlash+1, '\\');
	}

	return xFilename.GetString();
}

bool Tools_Mode_Cutscene::IsControllingCamera()
{
	if(!s_pxThis) return false;
	if(!s_pxThis->CutscenePlayback_Active) return false;
	if(!GetCurrentCutscene()) return false;
	if(Asura_ClientCutscene_Actor_Camera::Disable) return false;
	
	return GetCurrentCutscene()->IsControllingCamera();
}

void Tools_Mode_Cutscene::ListGuidsUsedByEntityAttachments(List<Asura_Guid>& xAttachmentList)
{
	//Search for all entity attachments in all cutscenes and list the entities used
	for(int i=0; i<Asura_Cutscene_System::GetNumberOfCutscenes(); i++)
	{
		Asura_Cutscene* pxCutscene = Asura_Cutscene_System::GetCutsceneByIndex(i);
		for(int j=0; j<pxCutscene->NumberOfActors; j++)
		{
			if(!pxCutscene->Actors[j]->IsHierarchyActor()) continue;

			Asura_Cutscene_Actor_Hierarchy* pxActor = (Asura_Cutscene_Actor_Hierarchy*) pxCutscene->Actors[j];
			for(int k=0; k<pxActor->m_iNumberOfAttachments; k++)
			{
				if(pxActor->m_ppxAttachments[k]->GetType() != AsuraCutsceneAttachment_Entity) continue;

				Asura_Cutscene_Attachment_Entity* pxAttachment = (Asura_Cutscene_Attachment_Entity*)pxActor->m_ppxAttachments[k];
				Asura_Guid uGuid = pxAttachment->GetAttachedGuid();
				if(uGuid != ASURA_GUID_UNREGISTERED && !xAttachmentList.contains(uGuid))
				{
					xAttachmentList.add_entry(uGuid);
				}
			}
		}
	}
}


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

void CutsceneModeUndo::PreUndoProcessing()
{
	//FR took out the cancelling of any flying thing. 
	//It does not seems to be necessary and is less confusing

	Tools_Mode_Cutscene::s_pxThis->RefreshPlayback();
}

void CutsceneModeUndo::PostUndoProcessing()
{
	if(Tools_Mode_Cutscene::CurrentCutscene)
	{
		Tools_Mode_Cutscene::CurrentCutscene->UpdateEventsAfterUndo();
	}
}


Tools_Cutscene_Object* GetCutsceneObject(const char* szName)
{
	List<Tools_Object*> xCutsceneList;
	Tools_Object_System::ListObjectsOfType(TOT_Cutscene, xCutsceneList);

	for(LIF<Tools_Object*> xCutsceneLIF(&xCutsceneList); !xCutsceneLIF.done(); xCutsceneLIF.next())
	{
		if(!stricmp(szName, xCutsceneLIF()->GetName()))
		{
			return (Tools_Cutscene_Object*) xCutsceneLIF();
		}
	}
	return 0;
}

Tools_CutsceneActor_Object* GetActorObject(const Asura_String& xActorName)
{
	Asura_Assert(Tools_Mode_Cutscene::CurrentCutscene,"");
	return Tools_Mode_Cutscene::CurrentCutscene->GetActor(xActorName.GetString());
}

Tools_Cutscene_Track* GetCutsceneTrack(const Asura_String& xActorName,int iTrackID)
{
	Tools_CutsceneActor_Object* pxActorObject = GetActorObject(xActorName);
	if(!pxActorObject) return 0;

	for(LIF<Tools_Cutscene_Track*> xTrackLIF(&pxActorObject->Tracks);!xTrackLIF.done();xTrackLIF.next())
	{
		if(xTrackLIF()->m_iTrackID == iTrackID) return xTrackLIF();
	}
	return 0;
}

Tools_CutsceneTrackPoint_Object* GetTrackPointObject(const Asura_String& xActorName,int iTrackID,int iPointID)
{
	Tools_Cutscene_Track* pxTrack = GetCutsceneTrack(xActorName,iTrackID);
	if(!pxTrack) return 0;

	for(LIF<Tools_CutsceneTrackPoint_Object*> xPointLIF(&pxTrack->m_xPointObjects); !xPointLIF.done(); xPointLIF.next())
	{
		if(xPointLIF()->m_iPointID == iPointID)
		{
			return xPointLIF();
		}
	}
	return 0;
}

Tools_Cutscene_Event* GetToolsCutsceneEvent(int id)
{
	Asura_Assert(Tools_Mode_Cutscene::CurrentCutscene,"");

	//search the event list for the one with the correct id
	for(LIF<Tools_Cutscene_Event*> elif(&Tools_Mode_Cutscene::CurrentCutscene->EventList);!elif.done();elif.next())
	{
		if(elif()->EventID == id)
		{
			return elif();
		}
	}
	return 0;
}
