//////////////////////////////////////////////////////////////////////////////
//
// ToolsAVP_Entity_SpawnPoint2.cpp
//
// Copyright (C) 2008 Rebellion. All Rights Reserved.
//
//////////////////////////////////////////////////////////////////////////////

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

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

// header
#include "Entity/Spawners/ToolsAVP_Entity_SpawnPoint2.h"

// asura
#include "AI/Asura_AI_Navigation.h"
#include "Blueprint/Asura_Blueprint_System.h"
#include "Core/Asura_String.h"
#include "Core/Memory/Asura_Mem.h"
#include "Interface/Asura_Dialog.h"
#include "Interface/Asura_Table_Dialog.h"

// axon

// avp
#include "Actor/Vertebrates/Alien/AVP_Alien_Description.h"
#include "Actor/Vertebrates/FaceHugger/AVP_FaceHugger_Description.h"
#include "Actor/Vertebrates/Human/AVP_Human_Description.h"
#include "Actor/Vertebrates/Predator/AVP_Predator_Description.h"
#include "Blueprint/AVP_Blueprint_HashIDs.h"
#include "Entity/AVP_Entity_EventIDs.h"
#include "Entity/AVP_Messages.h"
#include "Entity/Locations/OrientedNode/GamesceneNode/Tools_EntityNode_GS2Node.h"
#include "Entity/Spawners/AVP_ServerEntity_SpawnPoint2.h"

// tools
#include "ToolsAVP_ChooseObjects.h"
#include "Load&Save/Tools_SaveAsura.h"
#include "Misc/Tools_Timed_Messages.h"
#include "Objects/ToolsAVP_Object_Type.h"

//////////////////////////////////////////////////////////////////////////////
//
// ToolsAVP_TableCell_NumericalDialog
//
//////////////////////////////////////////////////////////////////////////////

class ToolsAVP_TableCell_NumericalDialog : public Asura_TableCell_Dialog
{
	typedef Asura_TableCell_Dialog PARENT;

public:
	ToolsAVP_TableCell_NumericalDialog( Asura_Table_Dialog& xTableDialogue, const Asura_Vector_2& xOffset,
										const Asura_Vector_2& xMinCellSize, const u_int uRow, const u_int uColumn,
										float* pfDialogVar, bool* pbChanged )
	: PARENT( xTableDialogue, xOffset, xMinCellSize, uRow, uColumn )
	{
		Asura_Vector_2 xLocalOffset( 0.0f, 0.0f );
		Asura_Dialog* pxDialog = new Asura_Numerical_Dialog( "", xLocalOffset, pfDialogVar, 0.0f, 10.0f, 0.05f, "%0.2f", pbChanged );
		AddDialogToPanel( pxDialog );
		xLocalOffset.x += pxDialog->m_xMaxCoord.x;
	}

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

//////////////////////////////////////////////////////////////////////////////
//
// ToolsAVP_Entity_SpawnPoint2
//
//////////////////////////////////////////////////////////////////////////////

TOOLS_DECLARE_SHARED_PARAM( int,   uTOOLSAVP_SHAREDPARAMHASH_SPAWN_MAXINPLAY,         Spawn_MaxInPlay );
TOOLS_DECLARE_SHARED_PARAM( int,   uTOOLSAVP_SHAREDPARAMHASH_SPAWN_MAXSPAWNCOUNT,     Spawn_MaxSpawnCount );
TOOLS_DECLARE_SHARED_PARAM( float, uTOOLSAVP_SHAREDPARAMHASH_SPAWN_SPAWNPERIODMIN,    Spawn_SpawnPeriodMin );
TOOLS_DECLARE_SHARED_PARAM( float, uTOOLSAVP_SHAREDPARAMHASH_SPAWN_SPAWNPERIODMAX,    Spawn_SpawnPeriodMax );
TOOLS_DECLARE_SHARED_PARAM( bool,  0xb50cf027, Spawn_UseVisibilityTest ); // Asura_GetHashID( "Spawn_UseVisibilityTest" )
TOOLS_DECLARE_SHARED_PARAM( bool,  0x59e2b1ae, Spawn_UseForDespawning );  // Asura_GetHashID( "Spawn_UseForDespawning" )

Asura_Hash_ID ToolsAVP_Entity_SpawnPoint2::s_uLastSpawneeTypeBPHashID = ASURA_HASH_ID_UNSET;
Asura_Hashed_List< ToolsAVP_Entity_SpawnPoint2 > ToolsAVP_Entity_SpawnPoint2::s_xList;

ToolsAVP_Entity_SpawnPoint2::ToolsAVP_Entity_SpawnPoint2( Asura_Entity* pxEntity )
: PARENT( pxEntity )
, m_bRefreshOverlay( false )
, m_uSpawneeTypeListSize( 0 )
, m_ppszSpawneeTypeList( NULL )
, m_pxSpawneeTypeTableDialog( NULL )
, m_uNewBlueprintTypeHashID( ASURA_HASH_ID_UNSET )
, m_uNewBlueprintHashID( ASURA_HASH_ID_UNSET )
, m_xGSOutNodeString()
, m_uNewGSOutNodeGuid( ASURA_GUID_UNREGISTERED )
, m_xGSInNodeString()
, m_uNewGSInNodeGuid( ASURA_GUID_UNREGISTERED )
{
	Type = TOT_Entity_SpawnPoint2;
	SetMesh( "ToolsShape_SpawnPoint" );

	s_xList.AddToList( this );
}

ToolsAVP_Entity_SpawnPoint2::~ToolsAVP_Entity_SpawnPoint2()
{
	// These shouldn't still be around, but just in case.
	DestroySpawneeTypeList();

	s_xList.RemoveFromList( this );
}

void ToolsAVP_Entity_SpawnPoint2::AddOverlayControls( Asura_Overlay* pxOverlay, Asura_Vector_2& xOffset )
{
	PARENT::AddOverlayControls( pxOverlay, xOffset );
	xOffset.y += Asura_Dialog::LargeSpacing() - Asura_Dialog::DefaultSpacing();


	Asura_Dialog* pxDialogue = NULL;
	AVP_ServerEntity_SpawnPoint2* pxSpawnPoint = GetSpawnPoint();

	pxDialogue = new Asura_Numerical_Dialog( "Max In Play", xOffset, &g_xParam_Spawn_MaxInPlay, 1, 32, 1, "%d" );
	pxDialogue->SetHelpText( "The max number of entities spawned from this controller allowed in play at a time." );
	pxOverlay->AddDialog( pxDialogue );
	xOffset.y += Asura_Dialog::DefaultSpacing();

	pxDialogue = new Asura_Numerical_Dialog( "Max To Spawn (-1 for infinite)", xOffset, &g_xParam_Spawn_MaxSpawnCount, -1, 999, 1, "%d" );
	pxDialogue->SetHelpText( "The max number of entities spawned from this controller in total." );
	pxOverlay->AddDialog( pxDialogue );
	xOffset.y += Asura_Dialog::DefaultSpacing();

	pxDialogue = new Asura_Numerical_Dialog( "Time Between Spawns", xOffset, &g_xParam_Spawn_SpawnPeriodMin, 0.0f, 999.0f, 0.1f, "%.1f" );
	pxOverlay->AddDialog( pxDialogue );
	xOffset.x += 23.0f;

	pxDialogue = new Asura_Numerical_Dialog( "", xOffset, &g_xParam_Spawn_SpawnPeriodMax, 0.0f, 999.0f, 0.1f, "%.1f" );
	pxOverlay->AddDialog( pxDialogue );
	xOffset.x -= 23.0f;
	xOffset.y += Asura_Dialog::LargeSpacing();


	pxDialogue = new Asura_SharedTickbox_Dialog( "Use Visibility Test", xOffset, &g_xParam_Spawn_UseVisibilityTest );
	pxDialogue->SetHelpText( "Whether the spawner should check it is not visible before allowing spawning." );
	pxOverlay->AddDialog( pxDialogue );
	xOffset.y += Asura_Dialog::DefaultSpacing();

	pxDialogue = new Asura_SharedTickbox_Dialog( "Use For Despawning", xOffset, &g_xParam_Spawn_UseForDespawning );
	pxDialogue->SetHelpText( "Whether the spawner can be used by entities to despawn." );
	pxOverlay->AddDialog( pxDialogue );
	xOffset.y += Asura_Dialog::LargeSpacing();


	pxDialogue = new Asura_Dialog( "Enemies To Spawn:", xOffset );
	pxOverlay->AddDialog( pxDialogue );
	xOffset.y += Asura_Dialog::DefaultSpacing();

	if ( pxSpawnPoint->m_uNumSpawneeTypes )
	{
		m_pxSpawneeTypeTableDialog = CreateSpawneeTypeTable( xOffset );
		pxOverlay->AddDialog( m_pxSpawneeTypeTableDialog );
		xOffset.y += m_pxSpawneeTypeTableDialog->GetTableSize().y + 0.5f;
	}

	pxDialogue = new Asura_Function_Dialog( "Add Enemy", xOffset, AddSpawneeType, reinterpret_cast< int >( this ) );
	pxOverlay->AddDialog( pxDialogue );
	xOffset.x += 18.0f;

	pxDialogue = new Asura_Function_Dialog( "Remove Selected", xOffset, RemoveSelectedSpawneeType, reinterpret_cast< int >( this ) );
	pxOverlay->AddDialog( pxDialogue );
	xOffset.x -= 18.0f;
	xOffset.y += Asura_Dialog::LargeSpacing();


	BuildGSOutNodeString();
	pxDialogue = new Asura_Function_Dialog( m_xGSOutNodeString.GetString(), xOffset, ChooseGSOutNode, reinterpret_cast< int >( this ) );
	pxDialogue->SetHelpText( "GS2 structure the spawned entity will start on." );
	pxOverlay->AddDialog( pxDialogue );
	xOffset.y += Asura_Dialog::DefaultSpacing();

	pxDialogue = new Asura_Function_Dialog( "Reset", xOffset, ResetGSOutNode, reinterpret_cast< int >( this ) );
	pxOverlay->AddDialog( pxDialogue );
	xOffset.y += Asura_Dialog::LargeSpacing();


	BuildGSInNodeString();
	pxDialogue = new Asura_Function_Dialog( m_xGSInNodeString.GetString(), xOffset, ChooseGSInNode, reinterpret_cast< int >( this ) );
	pxDialogue->SetHelpText( "GS2 structure the spawned entity will enter the spawn point and unspawn on." );
	pxOverlay->AddDialog( pxDialogue );
	xOffset.y += Asura_Dialog::DefaultSpacing();

	pxDialogue = new Asura_Function_Dialog( "Reset", xOffset, ResetGSInNode, reinterpret_cast< int >( this ) );
	pxOverlay->AddDialog( pxDialogue );
	xOffset.y += Asura_Dialog::DefaultSpacing();
}

void ToolsAVP_Entity_SpawnPoint2::Update()
{
	PARENT::Update();

	// If any of our data has changed then handle it.
	if ( m_uNewBlueprintTypeHashID != ASURA_HASH_ID_UNSET && m_uNewBlueprintHashID != ASURA_HASH_ID_UNSET )
	{
		AddNewSpawneeType( m_uNewBlueprintTypeHashID, m_uNewBlueprintHashID );
		m_uNewBlueprintTypeHashID = ASURA_HASH_ID_UNSET;
		m_uNewBlueprintHashID     = ASURA_HASH_ID_UNSET;
		Changed = true;
		RequestRefreshOverlay();
	}
	if ( m_uNewGSOutNodeGuid != ASURA_GUID_UNREGISTERED )
	{
		AVP_ServerEntity_SpawnPoint2* pxSpawnPoint = GetSpawnPoint();
		pxSpawnPoint->m_uGSOutNodeGuid = m_uNewGSOutNodeGuid;
		m_uNewGSOutNodeGuid = ASURA_GUID_UNREGISTERED;
		Changed = true;
		RequestRefreshOverlay();
	}
	if ( m_uNewGSInNodeGuid != ASURA_GUID_UNREGISTERED )
	{
		AVP_ServerEntity_SpawnPoint2* pxSpawnPoint = GetSpawnPoint();
		pxSpawnPoint->m_uGSInNodeGuid = m_uNewGSInNodeGuid;
		m_uNewGSInNodeGuid = ASURA_GUID_UNREGISTERED;
		Changed = true;
		RequestRefreshOverlay();
	}
}

void ToolsAVP_Entity_SpawnPoint2::OnSelection()
{
	VerifyGameObjectData();
	PARENT::OnSelection();
	m_bRefreshOverlay = false;
}

void ToolsAVP_Entity_SpawnPoint2::OnDeselection()
{
	DestroySpawneeTypeList();
	PARENT::OnDeselection();
}

void ToolsAVP_Entity_SpawnPoint2::UpdateWhileSelected()
{
	PARENT::UpdateWhileSelected();

	if ( m_bRefreshOverlay )
	{
		RefreshOverlay();
		m_bRefreshOverlay = false;
	}
}

bool ToolsAVP_Entity_SpawnPoint2::Verify( const bool bAutomatic )
{
	if ( !PARENT::Verify( bAutomatic ) )
	{
		return false;
	}

	// Make sure we're within a waypoint if we don't have a gs2 out link.
	AVP_ServerEntity_SpawnPoint2* pxSpawnPoint = GetSpawnPoint();

	if ( pxSpawnPoint->GetGamesceneOutNodeGuid() != ASURA_GUID_UNREGISTERED )
	{
		return true;
	}

	if ( Asura_AI_Navigation_System::FindWaypointNearestTo( pxSpawnPoint->GetPosition(), ASURA_NAVVERTEX_MAX_ZONE_ID, true ) == ASURA_INVALID_WAYPOINT_ID )
	{
		Tools_MessageLog::AddMessage( Tools_MessageLog::VERIFICATION, "Objects", new Tools_MessageLogAction_GotoEntity( GetGuid() ),
			"Spawn point %u has no GS2 out structure and is not within any waypoints.", GetGuid() );
		return false;
	}
	else
	{
		return true;
	}
}

bool ToolsAVP_Entity_SpawnPoint2::GetParameter( Asura_Hash_ID uParamID, Tools_Parameter* pxParam ) const
{
	switch ( uParamID )
	{
		case g_uHash_Spawn_MaxInPlay:         pxParam->SetInt( GetSpawnPoint()->m_iMaxInPlay );          return true;
        case g_uHash_Spawn_MaxSpawnCount:     pxParam->SetInt( GetSpawnPoint()->m_iMaxSpawnCount );      return true;
		case g_uHash_Spawn_SpawnPeriodMin:    pxParam->SetFloat( GetSpawnPoint()->m_fSpawnPeriodMin );   return true;
        case g_uHash_Spawn_SpawnPeriodMax:    pxParam->SetFloat( GetSpawnPoint()->m_fSpawnPeriodMax );   return true;
		case g_uHash_Spawn_UseVisibilityTest: pxParam->SetBool( GetSpawnPoint()->m_bUseVisibilityTest ); return true;
		case g_uHash_Spawn_UseForDespawning:  pxParam->SetBool( GetSpawnPoint()->m_bUseForDespawning );  return true;

		default: return PARENT::GetParameter( uParamID, pxParam );
	}
}

bool ToolsAVP_Entity_SpawnPoint2::SetParameter( Asura_Hash_ID uParamID, const Tools_Parameter* pxParam )
{
	switch ( uParamID )
	{
		case g_uHash_Spawn_MaxInPlay:
		{
			GetSpawnPoint()->m_iMaxInPlay = pxParam->GetInt();
			SetEntityModified();
			return true;
		}

		case g_uHash_Spawn_MaxSpawnCount:
		{
			GetSpawnPoint()->m_iMaxSpawnCount = pxParam->GetInt();
			SetEntityModified();
			return true;
		}

		case g_uHash_Spawn_SpawnPeriodMin:
		{
			GetSpawnPoint()->m_fSpawnPeriodMin = pxParam->GetFloat();
			SetEntityModified();
			return true;
		}

		case g_uHash_Spawn_SpawnPeriodMax:
		{
			GetSpawnPoint()->m_fSpawnPeriodMax = pxParam->GetFloat();
			SetEntityModified();
			return true;
		}

		case g_uHash_Spawn_UseVisibilityTest:
		{
			GetSpawnPoint()->m_bUseVisibilityTest = pxParam->GetBool();
			SetEntityModified();
			return true;
		}

		case g_uHash_Spawn_UseForDespawning:
		{
			GetSpawnPoint()->m_bUseForDespawning = pxParam->GetBool();
			SetEntityModified();
			return true;
		}

		default: return PARENT::SetParameter( uParamID, pxParam );
	}
}

void ToolsAVP_Entity_SpawnPoint2::Copy( const Tools_Object* pxObject )
{
	// Copy parent data.
	PARENT::Copy( pxObject );

	// Copy our data.
	const ToolsAVP_Entity_SpawnPoint2* pxFromObject = static_cast< const ToolsAVP_Entity_SpawnPoint2* >( pxObject );

	// Copy the engine entity's data across.
	AVP_ServerEntity_SpawnPoint2* pxToEntity   = static_cast< AVP_ServerEntity_SpawnPoint2* >( Entity );
	AVP_ServerEntity_SpawnPoint2* pxFromEntity = static_cast< AVP_ServerEntity_SpawnPoint2* >( pxFromObject->Entity );
	pxToEntity->m_uChunkedFlags      = pxFromEntity->m_uChunkedFlags;
	pxToEntity->m_iMaxInPlay		 = pxFromEntity->m_iMaxInPlay;
	pxToEntity->m_iMaxSpawnCount	 = pxFromEntity->m_iMaxSpawnCount;
	pxToEntity->m_fSpawnPeriodMin	 = pxFromEntity->m_fSpawnPeriodMin;
    pxToEntity->m_fSpawnPeriodMax	 = pxFromEntity->m_fSpawnPeriodMax;
    pxToEntity->m_uNumSpawneeTypes   = pxFromEntity->m_uNumSpawneeTypes;
	delete [] pxToEntity->m_pxSpawneeTypes;
	pxToEntity->m_pxSpawneeTypes = NULL;
	if ( pxFromEntity->m_uNumSpawneeTypes )
	{
		pxToEntity->m_pxSpawneeTypes = new AVP_ServerEntity_SpawnPoint2::BlueprintAndWeight[ pxFromEntity->m_uNumSpawneeTypes ];
		for ( u_int u = 0; u < pxFromEntity->m_uNumSpawneeTypes; ++u )
		{
			pxToEntity->m_pxSpawneeTypes[ u ] = pxFromEntity->m_pxSpawneeTypes[ u ];
		}
	}

	pxToEntity->m_uGSOutNodeGuid = pxFromEntity->m_uGSOutNodeGuid;
	pxToEntity->m_uGSInNodeGuid  = pxFromEntity->m_uGSInNodeGuid;

	pxToEntity->m_fSpawnTimer		= pxFromEntity->m_fSpawnTimer;
	pxToEntity->m_uTotalSpawnCount	= pxFromEntity->m_uTotalSpawnCount;
	pxToEntity->m_iSpawneeCount		= pxFromEntity->m_iSpawneeCount;
	pxToEntity->m_iSpawneeArraySize = pxFromEntity->m_iSpawneeArraySize;
	delete [] pxToEntity->m_pxSpawneeGuids;
	pxToEntity->m_pxSpawneeGuids = NULL;
	if ( pxFromEntity->m_iSpawneeArraySize )
	{
		pxToEntity->m_pxSpawneeGuids = new Asura_Guid[ pxFromEntity->m_iSpawneeArraySize ];
		for ( int i = 0; i < pxFromEntity->m_iSpawneeCount; ++i )
		{
			pxToEntity->m_pxSpawneeGuids[ i ] = pxFromEntity->m_pxSpawneeGuids[ i ];
		}
	}

	m_bRefreshOverlay = true;
}

Tools_Object* ToolsAVP_Entity_SpawnPoint2::CreateObjectOfThisType() const
{
	return CreateAndReturn();
}

bool ToolsAVP_Entity_SpawnPoint2::HandlesThisEvent( int iEventID ) const
{
	if ( iEventID == AVP_ENTITY_EVENT_SPAWN_ON_SPAWN ||
		iEventID == AVP_ENTITY_EVENT_SPAWN_ON_SPAWN_COUNT_EXHAUSTED || 
		iEventID == AVP_ENTITY_EVENT_SPAWN_ON_ANY_SPAWNEE_DEATH ||
		iEventID == AVP_ENTITY_EVENT_SPAWN_ON_ALL_SPAWNEES_DEAD ||
		iEventID == AVP_ENTITY_EVENT_DESPAWN_ON_SPAWN )
	{
		return true;
	}
	else
	{
		return PARENT::HandlesThisEvent( iEventID );
	}
}

bool ToolsAVP_Entity_SpawnPoint2::HandlesThisMessage( int iMessage )
{
	if ( iMessage == AVP_MSGID_SPAWN ||
		iMessage == AVP_MSGID_SPAWN_SET_MAX_IN_PLAY ||
		iMessage == AVP_MSGID_SPAWN_SET_MAX_SPAWN_COUNT ||
		iMessage == AVP_MSGID_SPAWN_RESET_SPAWN_COUNTER )
	{
		return true;
	}
	else
	{
		return PARENT::HandlesThisMessage( iMessage );
	}
}

void ToolsAVP_Entity_SpawnPoint2::AddFilesToExportList( List< Asura_String >& xNameList ) const
{
	PARENT::AddFilesToExportList( xNameList );

	static const Asura_Hash_ID s_uAlienBPHash      = 0x058996c7; // Asura_GetHashID( "Alien" )
	static const Asura_Hash_ID s_uFaceHuggerBPHash = 0x4633fcb7; // Asura_GetHashID( "FaceHugger" )
	static const Asura_Hash_ID s_uAndroidBPHash    = 0xcca8366f; // Asura_GetHashID( "Android" )
	static const Asura_Hash_ID s_uCivilianBPHash   = 0xadd8d383; // Asura_GetHashID( "Civilian" )
	static const Asura_Hash_ID s_uMarineBPHash     = 0xbf8c8dc2; // Asura_GetHashID( "Marine" )
	static const Asura_Hash_ID s_uPredatorBPHash   = 0xb31282f7; // Asura_GetHashID( "Predator" )

	AVP_ServerEntity_SpawnPoint2* pxSpawnPoint = GetSpawnPoint();
	for ( u_int u = 0; u < pxSpawnPoint->m_uNumSpawneeTypes; ++u )
	{
		const Asura_Hash_ID uBPTypeHashID = pxSpawnPoint->m_pxSpawneeTypes[ u ].m_uBPTypeHashID;
		const Asura_Hash_ID uBPHashID	  = pxSpawnPoint->m_pxSpawneeTypes[ u ].m_uBPHashID;

		Asura_Blueprint* pxBlueprint = Asura_Blueprint_System::GetBlueprintByHashID( uBPTypeHashID, uBPHashID );
		if ( !pxBlueprint )
		{
			continue;
		}

		if ( pxBlueprint->IsChildOf( uBPTypeHashID, s_uAlienBPHash, false ) )
		{
			AVP_Alien_Description::AddFilesToExport( false, uBPHashID, xNameList );
		}
		else if ( pxBlueprint->IsChildOf( uBPTypeHashID, s_uFaceHuggerBPHash, false ) )
		{
			AVP_FaceHugger_Description::AddFilesToExport( false, uBPHashID, xNameList );
		}
		else if ( pxBlueprint->IsChildOf( uBPTypeHashID, s_uAndroidBPHash, false ) )
		{
			AVP_Human_Description::AddAndroidFilesToExport( false, uBPHashID, xNameList );
		}
		else if ( pxBlueprint->IsChildOf( uBPTypeHashID, s_uCivilianBPHash, false ) )
		{
			AVP_Human_Description::AddCivilianFilesToExport( false, uBPHashID, xNameList );
		}
		else if ( pxBlueprint->IsChildOf( uBPTypeHashID, s_uMarineBPHash, false ) )
		{
			AVP_Human_Description::AddMarineFilesToExport( false, uBPHashID, xNameList );
		}
		else if ( pxBlueprint->IsChildOf( uBPTypeHashID, s_uPredatorBPHash, false ) )
		{
			AVP_Predator_Description::AddFilesToExport( false, uBPHashID, xNameList );
		}
	}
}

void ToolsAVP_Entity_SpawnPoint2::Create()
{
	CreateAndReturn();
}

ToolsAVP_Entity_SpawnPoint2* ToolsAVP_Entity_SpawnPoint2::CreateAndReturn()
{
	const Asura_Guid uGuid = Asura_Entity_System::GetCreatedByToolsGuid();

	Tools_Entity_Data* pxToolsData = CreateToolsDataWithDefaultName( "Spawn Point", "Spawn Point", uGuid );
	if ( !pxToolsData ) { return NULL; }

	AVP_ServerEntity_SpawnPoint2* pxSpawnPoint = new AVP_ServerEntity_SpawnPoint2( uGuid );
	if ( !pxSpawnPoint ) { return NULL; }
	pxSpawnPoint->ToolsData = pxToolsData;

	ToolsAVP_Entity_SpawnPoint2* pxToolsObject = new ToolsAVP_Entity_SpawnPoint2( pxSpawnPoint );
	if ( !pxToolsObject ) { return NULL; }
	Tools_Object_System::SelectObject( pxToolsObject );

	pxToolsObject->Position		= *Asura_Camera::GetPosition();
	pxToolsObject->Orientation  = Asura_Maths::IdentityMatrix3x3;
	pxToolsObject->m_uGuid		= uGuid;

	pxSpawnPoint->SetPosition( pxToolsObject->Position );
	pxSpawnPoint->SetOrientation( pxToolsObject->Orientation );

	Tools_Timed_Message_System::AddMessage( Tools_Timed_Message_System::COLOUR_DEFAULT, "Created new spawn point." );
	Tools_SaveAsura::SetEntityModified();

	return pxToolsObject;
}

Tools_Object* ToolsAVP_Entity_SpawnPoint2::Create( Asura_Entity* pxEntity )
{
	return new ToolsAVP_Entity_SpawnPoint2( pxEntity );
}

AVP_ServerEntity_SpawnPoint2* ToolsAVP_Entity_SpawnPoint2::GetSpawnPoint() const
{
	return static_cast< AVP_ServerEntity_SpawnPoint2* >( Entity );
}

void ToolsAVP_Entity_SpawnPoint2::VerifyGameObjectData()
{
	// Make sure everything referenced in the game object actually exists.
	AVP_ServerEntity_SpawnPoint2* pxSpawnPoint = GetSpawnPoint();

	// Spawnee types.
	for ( u_int u = 0; u < pxSpawnPoint->m_uNumSpawneeTypes;    )
	{
		const AVP_ServerEntity_SpawnPoint2::BlueprintAndWeight& xBPAndWeight = pxSpawnPoint->m_pxSpawneeTypes[ u ];

		Asura_Blueprint* pxBlueprint = Asura_Blueprint_System::GetBlueprintByHashID( xBPAndWeight.m_uBPTypeHashID, xBPAndWeight.m_uBPHashID );
		if ( !pxBlueprint )
		{
			RemoveSpawneeType( xBPAndWeight.m_uBPTypeHashID, xBPAndWeight.m_uBPHashID );
			Changed = true;
		}
		else
		{
			++u;
		}
	}

	// GS2 nodes.
	const Tools_EntityNode_GS2Node* pxGSOutNode = Tools_EntityNode_GS2Node::FindEntity( pxSpawnPoint->m_uGSOutNodeGuid );
	if ( !pxGSOutNode )
	{
		pxSpawnPoint->m_uGSOutNodeGuid = ASURA_GUID_UNREGISTERED;
	}
	const Tools_EntityNode_GS2Node* pxGSInNode = Tools_EntityNode_GS2Node::FindEntity( pxSpawnPoint->m_uGSInNodeGuid );
	if ( !pxGSInNode )
	{
		pxSpawnPoint->m_uGSInNodeGuid = ASURA_GUID_UNREGISTERED;
	}
}

void ToolsAVP_Entity_SpawnPoint2::RefreshOverlay()
{
	DeleteOverlay();
	CreateOverlay();
}

void ToolsAVP_Entity_SpawnPoint2::AddNewSpawneeType( const Asura_Hash_ID uBlueprintTypeHashID, const Asura_Hash_ID uBlueprintHashID )
{
	AVP_ServerEntity_SpawnPoint2* pxSpawnPoint = GetSpawnPoint();

	// Check it's not already there.
	for ( u_int u = 0; u < pxSpawnPoint->m_uNumSpawneeTypes; ++u )
	{
		if ( pxSpawnPoint->m_pxSpawneeTypes[ u ].m_uBPTypeHashID == uBlueprintTypeHashID &&
			pxSpawnPoint->m_pxSpawneeTypes[ u ].m_uBPHashID == uBlueprintHashID )
		{
			return;
		}
	}

	const u_int uOldCount = pxSpawnPoint->m_uNumSpawneeTypes;
	AVP_ServerEntity_SpawnPoint2::BlueprintAndWeight* pxOldArray = pxSpawnPoint->m_pxSpawneeTypes;
	const u_int uNewCount = uOldCount + 1;
	AVP_ServerEntity_SpawnPoint2::BlueprintAndWeight* pxNewArray = new AVP_ServerEntity_SpawnPoint2::BlueprintAndWeight[ uNewCount ];

	// Copy across.
	Asura_Mem::Copy( pxNewArray, pxOldArray, uOldCount * sizeof( AVP_ServerEntity_SpawnPoint2::BlueprintAndWeight ) );

	// Add new.
	pxNewArray[ uOldCount ].m_uBPTypeHashID = uBlueprintTypeHashID;
	pxNewArray[ uOldCount ].m_uBPHashID     = uBlueprintHashID;
	pxNewArray[ uOldCount ].m_fWeight	    = 1.0f;

	// Write back.
	pxSpawnPoint->m_uNumSpawneeTypes = uNewCount;
	pxSpawnPoint->m_pxSpawneeTypes   = pxNewArray;

	// Ditch old.
	delete [] pxOldArray;
}

void ToolsAVP_Entity_SpawnPoint2::RemoveSpawneeType( const Asura_Hash_ID uBlueprintTypeHashID, const Asura_Hash_ID uBlueprintHashID )
{
	AVP_ServerEntity_SpawnPoint2* pxSpawnPoint = GetSpawnPoint();

	const u_int uOldCount = pxSpawnPoint->m_uNumSpawneeTypes;
	AVP_ServerEntity_SpawnPoint2::BlueprintAndWeight* pxOldArray = pxSpawnPoint->m_pxSpawneeTypes;
	const u_int uNewCount = uOldCount - 1;

	if ( uNewCount == 0 )
	{
		// Remove all.
		delete [] pxSpawnPoint->m_pxSpawneeTypes;
		pxSpawnPoint->m_pxSpawneeTypes   = NULL;
		pxSpawnPoint->m_uNumSpawneeTypes = 0;
	}
	else
	{
		AVP_ServerEntity_SpawnPoint2::BlueprintAndWeight* pxNewArray = new AVP_ServerEntity_SpawnPoint2::BlueprintAndWeight[ uNewCount ];

		// Copy across.
		for ( u_int uReadIndex = 0, uWriteIndex = 0; uReadIndex < uOldCount; ++uReadIndex )
		{
			if ( pxOldArray[ uReadIndex ].m_uBPTypeHashID != uBlueprintTypeHashID ||
				pxOldArray[ uReadIndex ].m_uBPHashID != uBlueprintHashID )
			{
				pxNewArray[ uWriteIndex++ ] = pxOldArray[ uReadIndex ];
			}
		}

		// Write back.
		pxSpawnPoint->m_uNumSpawneeTypes = uNewCount;
		pxSpawnPoint->m_pxSpawneeTypes   = pxNewArray;

		// Ditch old.
		delete [] pxOldArray;
	}
}

Asura_Table_Dialog* ToolsAVP_Entity_SpawnPoint2::CreateSpawneeTypeTable( const Asura_Vector_2& xOffset )
{
	CreateSpawneeTypeList();

	AVP_ServerEntity_SpawnPoint2* pxSpawnPoint = GetSpawnPoint();

	Asura_Table_Dialog* pxTableDialogue = new Asura_Table_Dialog( xOffset, 40.0f, 10.0f, true );
	Asura_Vector_2 xCellSpacing = pxTableDialogue->GetCellSpacing();
	xCellSpacing.y = 0.25f;
	pxTableDialogue->SetCellSpacing( xCellSpacing );
	pxTableDialogue->EnableRowSelection( true );

	const Asura_Vector_2 xCellSize( 5.0f, 1.0f );
	pxTableDialogue->CreateTable( xCellSize, pxSpawnPoint->m_uNumSpawneeTypes, 1, CreateSpawneeTypeCell, this, m_ppszSpawneeTypeList );

	return pxTableDialogue;
}

void ToolsAVP_Entity_SpawnPoint2::CreateSpawneeTypeList()
{
	DestroySpawneeTypeList();

	AVP_ServerEntity_SpawnPoint2* pxSpawnPoint = GetSpawnPoint();
	m_uSpawneeTypeListSize = pxSpawnPoint->m_uNumSpawneeTypes;
    m_ppszSpawneeTypeList  = new char*[ m_uSpawneeTypeListSize ];

	for ( u_int u = 0; u < pxSpawnPoint->m_uNumSpawneeTypes; ++u )
	{
		const AVP_ServerEntity_SpawnPoint2::BlueprintAndWeight& xBPAndWeight = pxSpawnPoint->m_pxSpawneeTypes[ u ];

		Asura_Blueprint* pxBlueprint = Asura_Blueprint_System::GetBlueprintByHashID( xBPAndWeight.m_uBPTypeHashID, xBPAndWeight.m_uBPHashID );
		if ( pxBlueprint )
		{
			const u_int uNameLengthIncNull = strlen( pxBlueprint->GetName().GetString() ) + 1;
			m_ppszSpawneeTypeList[ u ] = new char[ uNameLengthIncNull ];
			Asura_PrintfToBfr( m_ppszSpawneeTypeList[ u ], "%s", uNameLengthIncNull, true, pxBlueprint->GetName().GetString() );
		}
		else
		{
			static const char* s_pszErrorString = "error";
			const u_int uNameLengthIncNull = strlen( s_pszErrorString ) + 1;
			m_ppszSpawneeTypeList[ u ] = new char[ uNameLengthIncNull ];
			Asura_PrintfToBfr( m_ppszSpawneeTypeList[ u ], "%s", uNameLengthIncNull, true, s_pszErrorString );
		}
	}
}

void ToolsAVP_Entity_SpawnPoint2::DestroySpawneeTypeList()
{
	if ( m_ppszSpawneeTypeList )
	{
		for ( u_int u = 0; u < m_uSpawneeTypeListSize; ++u )
		{
			delete [] m_ppszSpawneeTypeList[ u ];
			m_ppszSpawneeTypeList[ u ] = NULL;
		}
		delete [] m_ppszSpawneeTypeList;
        m_ppszSpawneeTypeList  = NULL;
		m_uSpawneeTypeListSize = 0;
	}
}

Asura_TableCell_Dialog* ToolsAVP_Entity_SpawnPoint2::CreateSpawneeTypeCell( Asura_Table_Dialog& xTableDialog,
																		    const Asura_Vector_2& xOffset, const Asura_Vector_2& xMinCellSize,
																			const u_int uRow, const u_int uColumn,
																			void* const pxUserData )
{
	ToolsAVP_Entity_SpawnPoint2*						pxThis			= static_cast< ToolsAVP_Entity_SpawnPoint2* >( pxUserData );
	AVP_ServerEntity_SpawnPoint2*						pxSpawnPoint	= pxThis->GetSpawnPoint();
	AVP_ServerEntity_SpawnPoint2::BlueprintAndWeight&	xBPAndWeight	= pxSpawnPoint->m_pxSpawneeTypes[ uRow ];

	return new ToolsAVP_TableCell_NumericalDialog( xTableDialog, xOffset, xMinCellSize, uRow, uColumn, &xBPAndWeight.m_fWeight, &pxThis->Changed );
}

void ToolsAVP_Entity_SpawnPoint2::AddSpawneeType( int iThis )
{
	const Asura_Hash_ID uActorBPHashID = Asura_GetHashID( "Actor" );
	Asura_Hash_ID uBlueprintHashID     = ( s_uLastSpawneeTypeBPHashID == ASURA_HASH_ID_UNSET ) ? uActorBPHashID : s_uLastSpawneeTypeBPHashID;
	Asura_Hash_ID uBlueprintTypeHashID = uAVP_BLUEPRINT_TYPE_ACTORS;

	BlueprintSelect_Overlay xBPOverlay( "Choose A Blueprint", &uBlueprintHashID, &uBlueprintTypeHashID, 1, &uBlueprintTypeHashID, 1, &uActorBPHashID );
	if ( !xBPOverlay.DoModal() ) { return; }

	ToolsAVP_Entity_SpawnPoint2* pxThis = reinterpret_cast< ToolsAVP_Entity_SpawnPoint2* >( iThis );
	pxThis->m_uNewBlueprintHashID     = uBlueprintHashID;
	pxThis->m_uNewBlueprintTypeHashID = uBlueprintTypeHashID;

	s_uLastSpawneeTypeBPHashID = uBlueprintHashID;
}

void ToolsAVP_Entity_SpawnPoint2::RemoveSelectedSpawneeType( int iThis )
{
	ToolsAVP_Entity_SpawnPoint2* pxThis = reinterpret_cast< ToolsAVP_Entity_SpawnPoint2* >( iThis );

	const int iSelectedRow = pxThis->m_pxSpawneeTypeTableDialog ? pxThis->m_pxSpawneeTypeTableDialog->GetCurrentlySelectedRow() : -1;
	if ( iSelectedRow != -1 )
	{
		AVP_ServerEntity_SpawnPoint2* pxSpawnPoint = pxThis->GetSpawnPoint();
		AVP_ServerEntity_SpawnPoint2::BlueprintAndWeight& xBPAndWeight = pxSpawnPoint->m_pxSpawneeTypes[ iSelectedRow ];
		pxThis->RemoveSpawneeType( xBPAndWeight.m_uBPTypeHashID, xBPAndWeight.m_uBPHashID );
		pxThis->RequestRefreshOverlay();
		pxThis->Changed = true;
	}
}

void ToolsAVP_Entity_SpawnPoint2::BuildGSOutNodeString()
{
	m_xGSOutNodeString = "Gamescene Out Node: ";

	AVP_ServerEntity_SpawnPoint2* pxSpawnPoint = GetSpawnPoint();
	if ( pxSpawnPoint->m_uGSOutNodeGuid == ASURA_GUID_UNREGISTERED )
	{
		m_xGSOutNodeString += "unset";
	}
	else
	{
		Tools_EntityNode_GS2Node* pxGS2NodeTools = Tools_EntityNode_GS2Node::FindEntity( pxSpawnPoint->m_uGSOutNodeGuid );
		if ( pxGS2NodeTools )
		{
			m_xGSOutNodeString += pxGS2NodeTools->GetName();
		}
		else
		{
			m_xGSOutNodeString += "error";
		}
	}
}

void ToolsAVP_Entity_SpawnPoint2::ChooseGSOutNode( int iThis )
{
	ToolsAVP_Entity_SpawnPoint2* pxThis = reinterpret_cast< ToolsAVP_Entity_SpawnPoint2* >( iThis );

	ToolsAVP_ChooseObject_ByType* pxChooseObject = new ToolsAVP_ChooseObject_ByType( TOT_Entity_AVPGS2Node, &pxThis->m_uNewGSOutNodeGuid );
	pxChooseObject->AddType( TOT_Entity_AVPGS2Attractor );
	Tools_Object_System::ChooseObject( "Select A GS2 Node", pxChooseObject );
}

void ToolsAVP_Entity_SpawnPoint2::ResetGSOutNode( int iThis )
{
	ToolsAVP_Entity_SpawnPoint2* pxThis = reinterpret_cast< ToolsAVP_Entity_SpawnPoint2* >( iThis );
	pxThis->GetSpawnPoint()->m_uGSOutNodeGuid = ASURA_GUID_UNREGISTERED;
	pxThis->RequestRefreshOverlay();
}

void ToolsAVP_Entity_SpawnPoint2::BuildGSInNodeString()
{
	m_xGSInNodeString = "Gamescene In Node: ";

	AVP_ServerEntity_SpawnPoint2* pxSpawnPoint = GetSpawnPoint();
	if ( pxSpawnPoint->m_uGSInNodeGuid == ASURA_GUID_UNREGISTERED )
	{
		m_xGSInNodeString += "unset";
	}
	else
	{
		Tools_EntityNode_GS2Node* pxGS2NodeTools = Tools_EntityNode_GS2Node::FindEntity( pxSpawnPoint->m_uGSInNodeGuid );
		if ( pxGS2NodeTools )
		{
			m_xGSInNodeString += pxGS2NodeTools->GetName();
		}
		else
		{
			m_xGSInNodeString += "error";
		}
	}
}

void ToolsAVP_Entity_SpawnPoint2::ChooseGSInNode( int iThis )
{
	ToolsAVP_Entity_SpawnPoint2* pxThis = reinterpret_cast< ToolsAVP_Entity_SpawnPoint2* >( iThis );

	ToolsAVP_ChooseObject_ByType* pxChooseObject = new ToolsAVP_ChooseObject_ByType( TOT_Entity_AVPGS2Node, &pxThis->m_uNewGSInNodeGuid );
	pxChooseObject->AddType( TOT_Entity_AVPGS2Attractor );
	Tools_Object_System::ChooseObject( "Select A GS2 Node", pxChooseObject );
}

void ToolsAVP_Entity_SpawnPoint2::ResetGSInNode( int iThis )
{
	ToolsAVP_Entity_SpawnPoint2* pxThis = reinterpret_cast< ToolsAVP_Entity_SpawnPoint2* >( iThis );
	pxThis->GetSpawnPoint()->m_uGSInNodeGuid = ASURA_GUID_UNREGISTERED;
	pxThis->RequestRefreshOverlay();
}

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