//
// Tools_Mode_Regions.cpp
//
// Created by Richard Rice - 15/10/2002
// 
// Copyright (C) 2002 Rebellion, All Rights Reserved.
//

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

#include "Core/Asura.h"

// This file's header.
#include "Regions/Tools_Mode_Regions.h"

// Engine independent.
#include "AI/Asura_AI_Navigation.h"
#include "Camera/Asura_Camera.h"
#include "Core/Asura_Maths.h"
#include "Core/Asura_UserInput.h"
#include "Debugging/Asura_Assert.h"
#include "Interface/Asura_Cursor.h"
#include "Interface/Asura_Dialog.h"
#include "Interface/Asura_Gadget.h"
#include "Interface/Asura_Menubar.h"
#include "Interface/Asura_Menubar_System.h"
#include "Interface/Asura_Overlay.h"
#include "Interface/Asura_Overlay_System.h"
#include "Mesh/Asura_CollisionMesh.h"
#include "Mesh/Asura_Env.h"
#include "Mesh/Asura_Env_System.h"
#include "Mesh/Asura_Mesh.h"
#include "Rendering/Asura_Module.h"
#include "Rendering/Asura_Text_Output.h"

// Tools.
#include "Tools_Overlays.h"
#include "Tools_System.h"
#include "Tools_Undo.h"
#include "Load&Save/LevelExport/Tools_Level_Export.h"
#include "Load&Save/Tools_SaveAsura.h"
#include "Misc/list_tem.hpp"
#include "Misc/Tools_Help.h"
#include "Misc/Tools_Module_Selection.h"
#include "Misc/Tools_Render.h"
#include "Misc/Tools_Render_Options.h"
#include "Misc/Tools_Timed_Messages.h"
#include "Movement/Tools_Movement.h"

// M A C R O S ///////////////////////////////////////////////////////////////

#define USE_WAYPOINTS_FOR_REQD_REGION_DETERMINATION (0)

// C O N S T A N T S /////////////////////////////////////////////////////////

static const char* g_aszRegionsModeHelpStrings [] =
{
    "F :           Apply the current region to selected module(s)",
    "R :           Switch to the region for the selected module",
    "T :           Switch to the region for the module the cursor is over",
    "",
    "N :           Previous region",
    "M :           Next Region",
    "",
    "J :           Previous module (when displaying required regions and single module is selected)",
    "K :           Next module     (                                \"                             )",
    "",
    "I :           Previous module in top module list (when top module list dialog is open)",
    "O :           Next module in top module list     (                 \"                 )",
    "",
    "Ctrl+Z :      Undo",
    "Ctrl+Y :      Redo",
    "< :           Undo",
    "> :           Redo",
	"",
	"Tab :         Toggle module linking",
	"",
	"Additional/altered keys for module linking",
	"B :           Set base module",
	"F :           Add link from base module(s) to selected module(s)",
	"Shift+F :     Remove link from base module(s) to selected module(s)",
	"Ctrl+Z :      Undo module linking",
	"Ctrl+Y :      Redo module linking",
	"< :           Undo module linking",
	"> :           Redo module linking",
	NULL
};

static const u_int g_auRegionColours[ASURA_ENV_MAX_REGIONS] =
{
    0xff0000,0x00ffff,0xffff00,0xff00ff,
    0x7d7d7d,0x732d00,0x002d73,0xffb982,
    0x82ffb9,0xb982ff,0x7dbe00,0xbe007d,
    0x00be7d,0x327df0,0x057805,0xeb7d0f,
    0x7d0feb,0xafff50,0xff50af,0x46ff50,
    0x5a005a,0xff3750,0x4b3caa,0x6ec3ff,
    0x41a041,0xb4b4b4,0x28ffaf,0xaa503c,
    0xa54bb9,0xb9a54b,0x0073aa,0xe1ffaa,
};

static const int MAX_MODULES_IN_TOP_MODULES_LIST = 20;

// T Y P E S /////////////////////////////////////////////////////////////////

struct ContributingModule
{

    // T Y P E S /////////////////////////////////////////////////////////////

    enum REASON
    {
        REASON_VIS,
        REASON_INTERSECTS,
        REASON_CLOSE,
    };

    // D A T A ///////////////////////////////////////////////////////////////
    
    u_int  uModuleIdx;
    u_int  uRegionsContribd;
    REASON eReason;

};


struct TopModuleListEntry
{
    u_int uModuleIdx;
    u_int uNumRegionsReqd;
};


struct UndoData
{

    // T Y P E S /////////////////////////////////////////////////////////////

    enum TYPE
    {
        TYPE_REGIONCHANGED,
        TYPE_FORCEINT = 0xffffffff,
    };

    // D A T A ///////////////////////////////////////////////////////////////
    
    TYPE     eType;
    int      iModuleIdx;
    unsigned uRegion;
    
};

struct ModuleLinkingUndoData
{

    // T Y P E S /////////////////////////////////////////////////////////////

    enum TYPE
    {
        TYPE_LINKCHANGED,
        TYPE_FORCEINT = 0xffffffff,
    };

    // D A T A ///////////////////////////////////////////////////////////////
    
    TYPE eType;
    int	iModuleIndex;
    int	iLinkedModuleIndex;
	bool bLinked;
    
};


// C L A S S E S /////////////////////////////////////////////////////////////

class RegionsModeUndo : public FixedSizeUndo
{

    public:

        // C O N S T R U C T O R S   /   D E S T R U C T O R S ///////////////

        RegionsModeUndo( void ) : FixedSizeUndo(10000, sizeof(UndoData)) { }

        // M E T H O D S /////////////////////////////////////////////////////
    
        void ProcessUndoData( void* pvData );
    
        void LogRegionChanged( int iModuleIndex );

};

class ModuleLinkingUndo : public FixedSizeUndo
{

    public:

        // C O N S T R U C T O R S   /   D E S T R U C T O R S ///////////////

        ModuleLinkingUndo( void ) : FixedSizeUndo(10000, sizeof(ModuleLinkingUndoData)) { }

        // M E T H O D S /////////////////////////////////////////////////////
    
        void ProcessUndoData( void* pvData );
        void LogLinkingChanged( int iModuleIndex, int iLinkedModuleIndex );

};



class RegionSelectorDialog : public Asura_Dialog
{

    public:

        // C O N S T R U C T O R S   /   D E S T R U C T O R S ///////////////

        RegionSelectorDialog( const Asura_Vector_2& xOffset, bool* pbTicked );

        // M E T H O D S /////////////////////////////////////////////////////

        void SetBackdropColourOverride( bool bOverride, u_int uBackdropColour );
        void SetRegion( int iRegion );

    private:

        // S T A T E /////////////////////////////////////////////////////////

        int                         m_iRegion;

        Asura_BoolTickbox_Gadget*   m_pxTickbox;
        Asura_StringDisplay_Gadget* m_pxStringDisplay;

        bool                        m_bBackdropOverride;
        u_int                       m_uBackdropColour;

};


class RegionSelectorOverlay : public Asura_Overlay
{

    public:

        // C O N S T R U C T O R S   /   D E S T R U C T O R S ///////////////

        RegionSelectorOverlay( void );

        // M E T H O D S /////////////////////////////////////////////////////

        void Refresh( void );

    protected:
        
        // S T A T E /////////////////////////////////////////////////////////

        RegionSelectorDialog* m_apxDialogs[ASURA_ENV_MAX_REGIONS];

};

class WaypointConnectivityCache
{

    public:

        // C L A S S   M E T H O D S /////////////////////////////////////////

        static bool Initialise( void );
        static void Shutdown( void );

        static float GetDistanceBetweenWPs( u_int uWP1, u_int uWP2 );

    private:
        
        // C L A S S   S T A T E /////////////////////////////////////////////

        static float* s_pfDistances;
        static u_int  s_uNumWPs;

};

// P R O T O T Y P E S   A N D   I N L I N E   F U N C T I O N S /////////////

// For passing to function gadgets.
static void AddIgnoreFlagToSelectedModules( void );
static void ApplyRegion( void );
static bool AreModulesOverlapping( const Asura_Bounding_Box& xBB1, const Asura_Bounding_Box& xBB2, float fBoundH, float fBoundV );
static bool CalculateRequiredRegions( void );
static void CreateRegionsOverlay( void );
static bool GenerateOverlappingModuleLists( List<List<int>*>* pxOverlappingModuleLists, int iStartModule, List<int>* pxCurList );
static void HideAllRegions( void );
static bool IsCurRegion( int iRegion );
static bool IsRegionsOverlayOn( void );
static void InvertDisplayedRegions( void );
static void OpenTopModulesDialog( void );
static void RemoveIgnoreFlagFromSelectedModules( void );
static void SelectRegionForModule( int iModuleIdx );
static void SetCurRegion( int iRegion );
static void ShowAllRegions( void );
static void SwitchToCurPosInTopModuleList( void );
static void ToggleRegionsOverlay( void );
#if USE_WAYPOINTS_FOR_REQD_REGION_DETERMINATION
static bool WaypointCallback( u_short usWaypointIdx );
#endif // USE_WAYPOINTS_FOR_REQD_REGION_DETERMINATION
static void SetBaseModule();
static void AddModuleLinks();
static void RemoveModuleLinks();
static bool IsDoingModuleLinking();

// Functions used for determining required regions.
static void AddToContributingModuleList( u_int uModule, u_int uContributingModule, u_int uRegionsContributed, ContributingModule::REASON eReason );
static const Asura_Bounding_Box& GetModuleBB( u_int uModule );
static bool IsModuleClose( u_int uModule, u_int uOtherModule );
static bool IsWaypointInModule( const Asura_AI_NavVertex* pxWP, const Asura_Module* pxModule );
static void SetupWorldspaceModuleBB( u_int uModuleIdx, Asura_Bounding_Box* pxBB );

// G L O B A L S /////////////////////////////////////////////////////////////

static u_int g_uCurRegion = 0;

static Asura_Handle g_iMenuHandle = ASURA_INVALID_HANDLE;

static Asura_Vector_2 g_xRegionsOverlayPosition( 3.0f, 28.0f );
static bool g_bRegionsOverlayOpen = true;

static RegionsModeUndo g_xUndo;
static ModuleLinkingUndo g_xModuleLinkUndo;

static RegionSelectorOverlay* g_pxRegionSelectorOverlay = NULL;
static bool g_abDisplayRegion[ASURA_ENV_MAX_REGIONS];

static Asura_Dialog* g_pxOpenTopModulesDialog               = NULL;
static Asura_Dialog* g_pxHighlightModulesThatMadeReqdDialog = NULL;
static Asura_Dialog* g_pxShowAllRegionsDialog               = NULL;
static Asura_Dialog* g_pxHideAllRegionsDialog               = NULL;
static Asura_Dialog* g_pxInvertRegionsDialog                = NULL;

static bool g_bRegionAssignmentModified = false;

static u_int g_uNumNonEmptyRegions = 0;
static u_int g_auNonEmptyRegions[ASURA_ENV_MAX_REGIONS];

static List<ContributingModule*>* g_pxContributingModuleLists = NULL;

// Highlight options.
static bool g_bOptionHliteAllRgns                 = false;
static bool g_bOptionHliteCurRgn                  = false;
static bool g_bOptionHliteReqdForSelectedModule   = false; // Only applicable when g_bHliteCurrent is on.
static bool g_bOptionHliteModulesThatForcedCurRgn = false; // Only applicable when g_bHliteCurrent is on.
static bool g_bOptionHliteCursorRgn               = false;
static bool g_bOptionHliteModulesThatReqCurRgn    = false;
static bool g_bOptionHliteModuleLinking			  = false;

// Display options.
static bool g_bOptionOnlyDispCurRegion            = false;
static bool g_bOptionDispIgnoreFlag               = false;

// Top module list state.
static List<TopModuleListEntry*> g_xTopModules;
static int g_iPosInTopModuleList = 0;

// Waypoint callback state.
static bool g_bFoundWaypointInBB;

// Module linking
List<int> g_xBaseModuleList;

// C L A S S   I M P L E M E N T A T I O N S /////////////////////////////////

//
// RegionsModeUndo.
//

void RegionsModeUndo::ProcessUndoData( void* pvData )
{
    UndoData* pxUndoData = reinterpret_cast<UndoData*>( pvData );
    switch( pxUndoData->eType )
    {
        case UndoData::TYPE_REGIONCHANGED:
        {
            Asura_Environment* pxEnv = Asura_Environment_System::GetEnvironment();

            //store current region and update from undo data
            u_int uRegion = pxEnv->GetModuleRegion( pxUndoData->iModuleIdx );
            pxEnv->SetModuleRegion( pxUndoData->iModuleIdx, pxUndoData->uRegion );

            //convert the undo data
            pxUndoData->uRegion = uRegion;

			Tools_SaveAsura::SetModuleModified(pxUndoData->iModuleIdx);

            break;
        }

        default :
            Asura_Assert( false, "Unexpected undo type for regions mode" );
            break;
    }
}

void RegionsModeUndo::LogRegionChanged( int iModuleIdx )
{
    // Allocate undo data.
    UndoData* pxUndo = reinterpret_cast<UndoData*>( AllocateNextUndoData() );
    if( !pxUndo ) { return; }

    // Grab ptr to env data.
    const Asura_Environment* pxEnv = Asura_Environment_System::GetEnvironment();
    if( !pxEnv ) { delete pxUndo; return; }
    
    // Fill in the undo data.
    pxUndo->eType      = UndoData::TYPE_REGIONCHANGED;
    pxUndo->iModuleIdx = iModuleIdx;
    pxUndo->uRegion    = pxEnv->GetModuleRegion( iModuleIdx );
}

//
// ModuleLinkingUndo.
//
void ModuleLinkingUndo::ProcessUndoData( void* pvData )
{
    ModuleLinkingUndoData* pxUndoData = reinterpret_cast<ModuleLinkingUndoData*>( pvData );
    switch( pxUndoData->eType )
    {
        case ModuleLinkingUndoData::TYPE_LINKCHANGED:
        {

			//store the current linked state , and update from undo data
			bool bLinked = Asura_Module_System::IsModuleLinked(pxUndoData->iModuleIndex,pxUndoData->iLinkedModuleIndex);
			Asura_Module_System::SetModuleLink(pxUndoData->iModuleIndex,pxUndoData->iLinkedModuleIndex,0,pxUndoData->bLinked);

	        //convert the undo data
            pxUndoData->bLinked = bLinked;

			//flag as modified
			Tools_SaveAsura::SetModuleLinkingModified();
			//will also have changed required regions ,so set as modified
	        g_bRegionAssignmentModified = true;

            break;
        }

        default :
            Asura_Assert( false, "Unexpected undo type for module linking" );
            break;
    }
}

void ModuleLinkingUndo::LogLinkingChanged( int iModuleIndex, int iLinkedModuleIndex )
{
    // Allocate undo data.
    ModuleLinkingUndoData* pxUndo = reinterpret_cast<ModuleLinkingUndoData*>( AllocateNextUndoData() );
    if( !pxUndo ) { return; }

    // Fill in the undo data.
    pxUndo->eType      = ModuleLinkingUndoData::TYPE_LINKCHANGED;
    pxUndo->iModuleIndex = iModuleIndex;
    pxUndo->iLinkedModuleIndex = iLinkedModuleIndex;
	pxUndo->bLinked = Asura_Module_System::IsModuleLinked(iModuleIndex,iLinkedModuleIndex);
}


//
// RegionSelectorDialog.
//

DEBUGVAR float REGIONSELECTORDIALOG_BORDER_X  = 0.0f;
DEBUGVAR float REGIONSELECTORDIALOG_BORDER_Y  = 0.0f;
DEBUGVAR float REGIONSELECTORDIALOG_GAP_X     = 0.0f;
DEBUGVAR u_int REGIONSELECTORDIALOG_MAX_CHARS = 2;

RegionSelectorDialog::RegionSelectorDialog( const Asura_Vector_2& xOffset, bool* pbTicked ) : Asura_Dialog( NULL, xOffset )
{
    m_iRegion = -1;

    Asura_Vector_2 xGadgetOffset( this->m_xOffset );

    m_pxTickbox = new Asura_BoolTickbox_Gadget( xGadgetOffset, pbTicked, NULL );
    AddGadget( m_pxTickbox );

    xGadgetOffset.x += m_pxTickbox->m_fWidth + REGIONSELECTORDIALOG_GAP_X;
    m_pxStringDisplay = new Asura_StringDisplay_Gadget( xGadgetOffset, REGIONSELECTORDIALOG_MAX_CHARS, IsCurRegion, SetCurRegion );
    AddGadget( m_pxStringDisplay );

    // Adjust offset of gadget with lower height.
    {
        float fHalfAbsDiff = 0.5f * Asura_Maths::Abs( m_pxTickbox->m_fHeight - m_pxStringDisplay->m_fHeight );
        if( fHalfAbsDiff )
        {
            if( m_pxTickbox->m_fHeight < m_pxStringDisplay->m_fHeight )
            {
                m_pxTickbox->m_xOffset.y += fHalfAbsDiff;
            }
            else
            {
                m_pxStringDisplay->m_xOffset.y += fHalfAbsDiff;
            }
        }
    }

    m_xMaxCoord.x = m_xOffset.x + REGIONSELECTORDIALOG_BORDER_X + m_pxTickbox->m_fWidth + REGIONSELECTORDIALOG_GAP_X + m_pxStringDisplay->m_fWidth + REGIONSELECTORDIALOG_BORDER_X;
    m_xMaxCoord.y = m_xOffset.y + REGIONSELECTORDIALOG_BORDER_Y + Asura_Maths::Max(m_pxTickbox->m_fHeight, m_pxStringDisplay->m_fHeight) + REGIONSELECTORDIALOG_BORDER_Y;
}

void RegionSelectorDialog::SetBackdropColourOverride( bool bOverride, u_int uBackdropColour )
{
    m_bBackdropOverride = bOverride;
    if( m_bBackdropOverride )
    {
        m_uBackdropColour = uBackdropColour;
    }
}

void RegionSelectorDialog::SetRegion( int iRegion )
{
    m_iRegion = iRegion;
    if( -1 == m_iRegion )
    {
        SetHidden( true );
    }
    else
    {
        Asura_Assert( 0 <= m_iRegion && static_cast<u_int>(m_iRegion) < ASURA_ENV_MAX_REGIONS, "" );

        SetHidden( false );

        if( m_pxTickbox )
        {
            m_pxTickbox->SetValue( &g_abDisplayRegion[m_iRegion] );
        }

        if( m_pxStringDisplay )
        {
            m_pxStringDisplay->SetCallbackArg( m_iRegion );

            char szNum[3];
            sprintf( szNum, "%i", m_iRegion );
            m_pxStringDisplay->SetLabel( szNum );
        }
    }
}

//
// RegionSelectorOverlay.
//

RegionSelectorOverlay::RegionSelectorOverlay( void ) : Asura_Overlay( "Region selector", false )
{
    m_uID                  = TOOLS_OVERLAY_REGIONS_REGIONSELECTOR;
    m_bAlwaysFullbright = true;
    m_bCanBeMovedByUser = false;

    Asura_Vector_2 xOffset( 0.0f, 0.0f );
    for( u_int u = 0; u < ASURA_ENV_MAX_REGIONS; ++u )
    {
        m_apxDialogs[u] = new RegionSelectorDialog( xOffset, &g_abDisplayRegion[u] );
        if( m_apxDialogs[u] )
        {
            AddDialog( m_apxDialogs[u] );
            xOffset.y = m_apxDialogs[u]->GetMaxCoords().y;
        }
    }
}

void RegionSelectorOverlay::Refresh( void )
{
    for( u_int u = 0; u < ASURA_ENV_MAX_REGIONS; ++u )
    {
        if( m_apxDialogs[u] )
        {
            m_apxDialogs[u]->SetRegion( u < g_uNumNonEmptyRegions ? g_auNonEmptyRegions[u] : -1 );
        }
    }

    CalculateOverlaySize();

    SetPosition( Asura_Overlay_System::GetMaxScreenX() - m_fWidth, 2.0f, true );
}

//
// WaypointConnectivityCache.
//

float* WaypointConnectivityCache::s_pfDistances = NULL;
u_int  WaypointConnectivityCache::s_uNumWPs;

bool WaypointConnectivityCache::Initialise( void )
{
    Asura_Assert( Asura_AI_Navigation_System::GetNumberOfWaypoints() > 0, "Shouldn't be using this class if we don't have any nav data." );
    s_uNumWPs = Asura_AI_Navigation_System::GetNumberOfWaypoints();

    s_pfDistances = new float[s_uNumWPs * s_uNumWPs];
    if( !s_pfDistances )
    {
        return false;
    }

    float* pfOut = &s_pfDistances[0];
    for( int i = s_uNumWPs * s_uNumWPs; i--; )
    {
        *pfOut++ = -1.0f;
    }

    return true;
}

float WaypointConnectivityCache::GetDistanceBetweenWPs( u_int uWP1, u_int uWP2 )
{
    Asura_Assert( uWP1 < s_uNumWPs && uWP2 < s_uNumWPs, "Invalid waypoint index/indices." );
    Asura_Assert( s_pfDistances, "Not initialised or initialisation failed." );

    // Already calculated?
    float& fEntry = s_pfDistances[uWP1 * s_uNumWPs + uWP2];
    if( fEntry >= 0.0f )
    {
        return fEntry;
    }

    // Same WP?
    if( uWP1 == uWP2 )
    {
        fEntry = 0.0f;
        return fEntry;
    }

    enum { MAX_ROUTE_LENGTH = 128 };
    unsigned short ausRoute[ MAX_ROUTE_LENGTH ];
    int            iRouteLength;
    Asura_AI_Navigation_System::FindRoute( ausRoute, iRouteLength, MAX_ROUTE_LENGTH,
        static_cast< unsigned short >( uWP1 ), static_cast< unsigned short >( uWP2 ), 0, NULL );

    // Connected at all?
    if ( iRouteLength == 0 )
    {
        fEntry = Asura_Maths::LargeFloat;
        return fEntry;
    }

    // Calculate distance.
    {
        fEntry = 0.0f;

        const Asura_AI_NavVertex* pxPrevWP = Asura_AI_Navigation_System::GetWaypoint( static_cast< unsigned short >( uWP1 ) );
        for( int i = 1; i < iRouteLength; ++i )
        {
            const unsigned short usThisWP = ausRoute[ i ];

            // Find connection.
            int j = 0;
            for( ; j < pxPrevWP->m_ucNumConnections; ++j )
            {
                if( pxPrevWP->m_pxConnections[j].m_usIndex == usThisWP )
                {
                    fEntry += pxPrevWP->m_pxConnections[j].GetLength();
                    break;
                }
            }
            Asura_Assert( j < pxPrevWP->m_ucNumConnections, "" );

            pxPrevWP = Asura_AI_Navigation_System::GetWaypoint( usThisWP );
        }
    }

    return fEntry;
}

void WaypointConnectivityCache::Shutdown( void )
{
    if( s_pfDistances )
    {
        delete[] s_pfDistances;
        s_pfDistances = NULL;
    }
}

//
// Tools_Mode_Regions.
//

// (MP - 25.02.2003) This method currently assumes it will only be called
// during a PS2 level export, so that it can call Tools_Level_Export's
// IsPS2MultiplayerExport() method.
u_int Tools_Mode_Regions::CalculateMaxReqdDmaChainPieces(  const u_int* puNumDmaChainPiecesPerRgn,
                                                           u_int* puRegionsForMaxChainPieces,
                                                           List<int>* pxCausingModuleList,
                                                           u_int* puOtherRegionsForMaxChainPieces,
                                                           List<int>* pxOtherCausingModuleList )
{
    u_int uMaxPiecesReqd = 0;

    const Asura_Environment* pxEnv = Asura_Environment_System::GetEnvironment();
    if( !pxEnv )
    {
        Tools_Timed_Message_System::AddMessage( 0xff0000, "ERROR: Environment doesn't appear to be loaded." );
        return 0;
    }

    // Deal with the simple case of dynamic loading not being used.
    {
        bool bAnyNotInRegion0 = false;
        for( int iModule = 0; iModule < Asura_Module_System::GetNumberOfModules(); ++iModule )
        {
            if( 0 != pxEnv->GetModuleRegion(iModule) )
            {
                bAnyNotInRegion0 = true;
                break;
            }
        }

        if( !bAnyNotInRegion0 )
        {
            *puRegionsForMaxChainPieces = (1 << 0);
            *pxCausingModuleList        = 0;

            if( Tools_Level_Export::IsPS2MultiplayerExport() )
            {
                *puOtherRegionsForMaxChainPieces = 0;
                *pxOtherCausingModuleList        = 0;
            }
            
            return puNumDmaChainPiecesPerRgn[0];
        }
    }

#if USE_WAYPOINTS_FOR_REQD_REGION_DETERMINATION
    if( Asura_AI_Navigation_System::GetNumberOfWaypoints() <= 0 )
    {
        Tools_Timed_Message_System::AddMessage( 0xff0000, "ERROR: You haven't generated waypoints for this level. These are required for dynamic loading calculations." );
        return 0;
    }
#endif //USE_WAYPOINTS_FOR_REQD_REGION_DETERMINATION

    // Prepare a list of lists of overlapping modules. Each of these lists
    // contains modules that all overlap one another. Since we're using AABBs
    // this means that there exists a point that lies in all of the modules
    // in a given list. Note that this code doesn't currently make any
    // attempt to ensure this point is somewhere that's reachable by the
    // player, etc.; instead we assume that modules that are completely
    // unreachable won't have any module linking data anyway.
    List<List<int>*> xOverlappingModuleLists;
    GenerateOverlappingModuleLists( &xOverlappingModuleLists, 0, NULL );

    // Now iterate through these lists. For each, calculate the memory
    // necessary to load all the regions required by all of the modules; this
    // then accounts for the camera being at points inside the union of all
    // their BBs.
    for( CLIF<List<int>*> xListIt(&xOverlappingModuleLists); !xListIt.done(); xListIt.next() )
    {
        const List<int>* pxList = xListIt();

        u_int uPiecesReqd = 0;
        u_int uCounted    = 0;
        for( CLIF<int> xModuleIt(pxList); !xModuleIt.done(); xModuleIt.next() )
        {
            u_int uReqdRegions = pxEnv->GetRequiredRegions( xModuleIt() );
            uReqdRegions &= ~uCounted;

            if( uReqdRegions )
            {
                for( u_int uRgn = 0; uRgn < ASURA_ENV_MAX_REGIONS; ++uRgn )
                {
                    if( uReqdRegions & (1 << uRgn) ) { uPiecesReqd += puNumDmaChainPiecesPerRgn[uRgn]; }
                }
                uCounted |= uReqdRegions;
            }
        }

        // If this is a cooperative export then we need to look through all
        // other module lists to find the two points that produce the highest
        // required regions. This is due to the fact that the two players may
        // go and stand in completely different positions.
        // (MP - 25.02.2003) We'll assume for now that a true return value
        // from IsPS2MultiplayerExport() indicates cooperative. If it's some
        // other multiplayer mode then we won't be doing dynamic loading
        // anyway.
        if( Tools_Level_Export::IsPS2MultiplayerExport() )
        {
            Asura_Assert( puOtherRegionsForMaxChainPieces, "" );
            Asura_Assert( pxOtherCausingModuleList,        "" );

            CLIF<List<int>*> xOtherListIt = xListIt.duplicate();
            for( xOtherListIt.next(); !xOtherListIt.done(); xOtherListIt.next() )
            {
                const List<int>* pxOtherList = xOtherListIt();

                u_int uOtherPiecesReqd = 0;
                u_int uOtherCounted    = 0;
                for( CLIF<int> xModuleIt(pxOtherList); !xModuleIt.done(); xModuleIt.next() )
                {
                    u_int uOtherReqdRegions = pxEnv->GetRequiredRegions( xModuleIt() );
                    uOtherReqdRegions &= ~(uCounted | uOtherCounted);

                    if( uOtherReqdRegions )
                    {
                        for( u_int uRgn = 0; uRgn < ASURA_ENV_MAX_REGIONS; ++uRgn )
                        {
                            if( uOtherReqdRegions & (1 << uRgn) ) { uOtherPiecesReqd += puNumDmaChainPiecesPerRgn[uRgn]; }
                        }
                        uOtherCounted |= uOtherReqdRegions;
                    }
                }

                if( uPiecesReqd + uOtherPiecesReqd > uMaxPiecesReqd )
                {
                    uMaxPiecesReqd                   = uPiecesReqd + uOtherPiecesReqd;
                    *puRegionsForMaxChainPieces      = uCounted;
                    *pxCausingModuleList             = *pxList;
                    *puOtherRegionsForMaxChainPieces = uOtherCounted;
                    *pxOtherCausingModuleList        = *pxOtherList;
                }
            }
        }
        else
        {
            if( uPiecesReqd > uMaxPiecesReqd )
            {
                uMaxPiecesReqd              = uPiecesReqd;
                *puRegionsForMaxChainPieces = uCounted;
                *pxCausingModuleList        = *pxList;
            }
        }
    }

    // Free all the lists we've allocated.
    while( xOverlappingModuleLists.size() > 0 )
    {
        delete xOverlappingModuleLists.last_entry();
        xOverlappingModuleLists.delete_last_entry();
    }

    return uMaxPiecesReqd;
}

void Tools_Mode_Regions::DealWithModifiedModuleLinks( void )
{
	//Disabled this since the required region stuff isn't used by any current project , and this
	//just results in the .asr file being flagged as modified unecessarily.
	//RAR 22/10/2007
    #if 0
    CalculateRequiredRegions();

    Tools_SaveAsura::SetMainFileModified();
    if( !Tools_SaveAsura::CanSaveFile(Tools_SaveAsura::GetMainFilename(), false) )
    {
        Tools_Timed_Message_System::AddMessage( 0xFFFF00, "Error : You'll need to check out the main file because module linking affects region data." );
    }
	#endif
}

void Tools_Mode_Regions::Initialise(void)
{
    //Initalise module selection
    Tools_Module_Selection::Activate();

    g_xUndo.Initialise();
    g_xModuleLinkUndo.Initialise();

    // Create the menubar.
    g_iMenuHandle = Asura_Menubar_System::CreateMenuSection();
    Asura_Menubar_Title* title;

    title = Asura_Menubar_System::AddTitle( g_iMenuHandle, "Overlays" );
    title->AddItem( "Regions", ToggleRegionsOverlay, IsRegionsOverlayOn );
    title->AddItem( "Selection", Tools_Module_Selection::ToggleSelectionOverlay, Tools_Module_Selection::SelectionOverlayIsOn );

    // Add help.
    Tools_Help::AddHelpTopic( "Regions Mode", g_aszRegionsModeHelpStrings );

    // Create overlays.
    CreateRegionsOverlay();

    // Ensure we recalculate reqd regions first time.
    g_bRegionAssignmentModified = true;

    // All regions displayed by default.
    for( u_int u = 0; u < ASURA_ENV_MAX_REGIONS; ++u )
    {
        g_abDisplayRegion[u] = true;
    }
}

void Tools_Mode_Regions::Shutdown(void)
{
    g_pxOpenTopModulesDialog               = NULL;
    g_pxHighlightModulesThatMadeReqdDialog = NULL;
    g_pxShowAllRegionsDialog               = NULL;
    g_pxHideAllRegionsDialog               = NULL;
    g_pxInvertRegionsDialog                = NULL;

    if( g_pxContributingModuleLists )
    {
        // (MP - 25.10.2002) TODO: Need to worry about module count changing?
        for( u_int u = Asura_Module_System::GetNumberOfModules(); u--; )
        {
            while( g_pxContributingModuleLists[u].size() )
            {
                delete g_pxContributingModuleLists[u].first_entry();
                g_pxContributingModuleLists[u].delete_first_entry();
            }
        }

        delete[] g_pxContributingModuleLists;
        g_pxContributingModuleLists = NULL;
    }

    while( g_xTopModules.size() )
    {
        delete g_xTopModules.last_entry();
        g_xTopModules.delete_last_entry();
    }

    Tools_Help::RemoveHelpTopic("Regions Mode");

    Tools_Module_Selection::Deactivate();

    g_xUndo.Shutdown();
    g_xModuleLinkUndo.Shutdown();


    //shutdown the menu
    if( g_iMenuHandle != ASURA_INVALID_HANDLE )
    {
        Asura_Menubar_System::DestroyMenuSection( g_iMenuHandle );
        g_iMenuHandle = ASURA_INVALID_HANDLE;
    }

    //close overlays
    if( Asura_Overlay_System::DoesOverlayExist(TOOLS_OVERLAY_REGIONS_TOPMODULES) )
    {
        Asura_Overlay_System::CloseOverlays( TOOLS_OVERLAY_REGIONS_TOPMODULES );
    }
    Asura_Overlay_System::CloseOverlays( TOOLS_OVERLAY_REGIONS_REGIONSELECTOR );
    Asura_Overlay_System::CloseOverlays( TOOLS_OVERLAY_MODE );

    //unhide modules
    const Asura_Environment* pxEnv = Asura_Environment_System::GetEnvironment();
    Asura_Assert( pxEnv, "" );
    for( u_int uModule = pxEnv->m_iNumberOfModules; uModule--; )
    {
        Tools_Render_Options::RemoveRenderFlagModule( uModule, ToolsRenderFlag_ModeHide );
    }

	//clear base module list
	while(g_xBaseModuleList.size()) g_xBaseModuleList.delete_first_entry();

}

void Tools_Mode_Regions::Update(void)
{
	#if defined ASURA_ASSERT
		const Asura_Environment* pxEnv =
	#endif
		Asura_Environment_System::GetEnvironment();

    Asura_Assert( pxEnv, "" );

    if( g_bRegionAssignmentModified )
    {
        CalculateRequiredRegions();

        if( g_pxRegionSelectorOverlay ) { g_pxRegionSelectorOverlay->Refresh(); }

        g_bRegionAssignmentModified = false;
    }

    // (MP - 23.10.2002) I think we want certain highlight flags to be
    // mutually exclusive.
    {
        static bool bHighlightAllWasOn           = g_bOptionHliteAllRgns;
        static bool bHighlightCurWasOn           = g_bOptionHliteCurRgn;
        static bool bHighlightReqdWasOn          = g_bOptionHliteReqdForSelectedModule;
        static bool bHighlightCursorRegionWasOn  = g_bOptionHliteCursorRgn;
        static bool bHighlightModulesReqCurWasOn = g_bOptionHliteModulesThatReqCurRgn;

        if( g_bOptionHliteAllRgns && !bHighlightAllWasOn )
        {
            g_bOptionHliteCurRgn                = false;
            g_bOptionHliteReqdForSelectedModule = false;
            g_bOptionHliteCursorRgn             = false;
            g_bOptionHliteModulesThatReqCurRgn  = false;
        }
        else if( g_bOptionHliteCurRgn && !bHighlightCurWasOn )
        {
            // (MP - 12.02.2003) NB: Allow this to co-exist with the
            // "highlight modules that require current region" option.
            g_bOptionHliteAllRgns               = false;
            g_bOptionHliteReqdForSelectedModule = false;
            g_bOptionHliteCursorRgn             = false;
        }
        else if( g_bOptionHliteReqdForSelectedModule && !bHighlightReqdWasOn )
        {
            g_bOptionHliteAllRgns               = false;
            g_bOptionHliteCurRgn                = false;
            g_bOptionHliteCursorRgn             = false;
            g_bOptionHliteModulesThatReqCurRgn  = false;
        }
        else if( g_bOptionHliteCursorRgn && !bHighlightCursorRegionWasOn )
        {
            g_bOptionHliteAllRgns               = false;
            g_bOptionHliteCurRgn                = false;
            g_bOptionHliteReqdForSelectedModule = false;
            g_bOptionHliteModulesThatReqCurRgn  = false;
        }
        else if( g_bOptionHliteModulesThatReqCurRgn && !bHighlightModulesReqCurWasOn )
        {
            // (MP - 12.02.2003) NB: Allow this to co-exist with the
            // "highlight current region" option.
            g_bOptionHliteAllRgns               = false;
            g_bOptionHliteReqdForSelectedModule = false;
            g_bOptionHliteCursorRgn             = false;
        }

        if( bHighlightReqdWasOn && !g_bOptionHliteReqdForSelectedModule )
        {
            // Close top modules overlay if this has been turned off because
            // it will no longer be any use.
            if( Asura_Overlay_System::DoesOverlayExist(TOOLS_OVERLAY_REGIONS_TOPMODULES) )
            {
                Asura_Overlay_System::CloseOverlays( TOOLS_OVERLAY_REGIONS_TOPMODULES );
            }
        }

        bHighlightAllWasOn           = g_bOptionHliteAllRgns;
        bHighlightCurWasOn           = g_bOptionHliteCurRgn;
        bHighlightReqdWasOn          = g_bOptionHliteReqdForSelectedModule;
        bHighlightCursorRegionWasOn  = g_bOptionHliteCursorRgn;
        bHighlightModulesReqCurWasOn = g_bOptionHliteModulesThatReqCurRgn;
    }

    // (MP - 24.10.2002) Certain flags only work in conjunction with others...
    g_pxOpenTopModulesDialog->SetEnabled( g_bOptionHliteReqdForSelectedModule );
    g_pxHighlightModulesThatMadeReqdDialog->SetEnabled( g_bOptionHliteReqdForSelectedModule );
    g_pxShowAllRegionsDialog->SetEnabled( !g_bOptionOnlyDispCurRegion );
    g_pxHideAllRegionsDialog->SetEnabled( !g_bOptionOnlyDispCurRegion );
    g_pxInvertRegionsDialog->SetEnabled( !g_bOptionOnlyDispCurRegion );

    //Flag the undo to start a new undo batch
    g_xUndo.MarkUndoStart();
    g_xModuleLinkUndo.MarkUndoStart();

    Tools_Module_Selection::Update();

    // Update state related to "only display cur region" option.
    {
        static bool  bOnlyDispCurRgnWasOn = g_bOptionOnlyDispCurRegion;
        static u_int uLastRegion          = g_uCurRegion;

        if( bOnlyDispCurRgnWasOn && !g_bOptionOnlyDispCurRegion )
        {
            for( u_int u = ASURA_ENV_MAX_REGIONS; u--; ) { g_abDisplayRegion[u] = true; }
        }
        else if( g_bOptionOnlyDispCurRegion && (!bOnlyDispCurRgnWasOn || g_uCurRegion != uLastRegion) )
        {
            for( u_int u = ASURA_ENV_MAX_REGIONS; u--; ) { g_abDisplayRegion[u] = ( u == g_uCurRegion ); }
        }

        bOnlyDispCurRgnWasOn = g_bOptionOnlyDispCurRegion;
        uLastRegion          = g_uCurRegion;
    }

    //act upon keyboard input
    Tools_Mode_Regions::HandleKeyboard();
}

void Tools_Mode_Regions::RenderWorld_PostToneMap( void )
{
    const Asura_Environment* pxEnv = Asura_Environment_System::GetEnvironment();
    Asura_Assert( pxEnv, "" );

    // Basic module highlighting options.

	if( g_bOptionHliteModuleLinking)
	{
		for(int iModule = 0; iModule < pxEnv->m_iNumberOfModules; iModule++)
		{
			u_int uHighlightColour = 0;
			if(g_xBaseModuleList.contains(iModule))
			{
				uHighlightColour = 0xff0000ff;
			}
			else
			{
				//is this module linked to any of the base modules?
				for(LIF<int> xBaseLIF(&g_xBaseModuleList);!xBaseLIF.done();xBaseLIF.next())
				{
					if(Asura_Module_System::IsModuleLinked(xBaseLIF(),iModule))
					{
						uHighlightColour = 0xff00ff00;
						break;
					}
				}
			}
			if( uHighlightColour )
			{
			    Tools_Render::RenderMeshOutline( iModule, uHighlightColour );
			}
		}
		Asura_Text_Output::Print( 1.0f, 13.0f, 0xffffffff, 0, "   Module linking:" );
		Asura_Text_Output::Print( 1.0f, 14.0f, 0xff0000ff, 0, "      Base modules" );
		Asura_Text_Output::Print( 1.0f, 15.0f, 0xff00ff00, 0, "      Linked modules" );
	}
	else
	{
    	for( int iModule = 0; iModule < pxEnv->m_iNumberOfModules; ++iModule )
    	{
    	    // (MP - 12.02.2003) NB: These options are confined to being mutually
    	    // exclusive as necessary elsewhere.

    	    if( g_bOptionHliteModulesThatReqCurRgn )
    	    {
    	        if( pxEnv->GetRequiredRegions(iModule) & (1 << g_uCurRegion) ) { Tools_Render::RenderMeshOutline( iModule, 0xFF0000FFu ); } // Blue.
    	    }

    	    if( g_bOptionHliteAllRgns )
    	    {
    	        Tools_Render::RenderMeshOutline( iModule, 0xff000000 | g_auRegionColours[pxEnv->GetModuleRegion(iModule) % ASURA_ENV_MAX_REGIONS] );
    	    }

    	    if( g_bOptionHliteCurRgn )
    	    {
    	        if( pxEnv->GetModuleRegion(iModule) == g_uCurRegion ) { Tools_Render::RenderMeshOutline( iModule, 0xFF00FF00u ); } // Green.
    	    }
    	}

    	// Other more-complex module highlighting options.
    
    	if( g_bOptionHliteReqdForSelectedModule )
    	{
    	    float fY = 10.0f;

    	    Asura_Text_Output::Print( 1.0f, fY++, 0xffffffff, 0, "Required region highlighting:" );

    	    const List<int>* pxSelectedModuleList = Tools_Module_Selection::GetSelectionList();
    	    if( 1 == pxSelectedModuleList->size() )
    	    {
    	        int iSelectedModule = pxSelectedModuleList->first_entry();
    	        Asura_Assert( 0 <= iSelectedModule && iSelectedModule < pxEnv->m_iNumberOfModules, "" );

    	        u_int uRegionsToHighlight = pxEnv->GetRequiredRegions( iSelectedModule );

    	        for( int iModule = 0; iModule < pxEnv->m_iNumberOfModules; ++iModule )
    	        {
    	            u_int uRegionForThisModule = pxEnv->GetModuleRegion( iModule );
    	            Asura_Assert( uRegionForThisModule < ASURA_ENV_MAX_REGIONS, "" );

    	            u_int uHighlightColour = 0;

    	            if( g_bOptionHliteModulesThatForcedCurRgn )
    	            {
    	                // Is the current region actually required?
    	                if( uRegionsToHighlight & (1 << g_uCurRegion) )
    	                {
    	                    // Did this module force the selected module to
    	                    // require the current region?
    	                    CLIF<ContributingModule*> xIt( &g_pxContributingModuleLists[iSelectedModule] );
    	                    for( ; !xIt.done(); xIt.next() )
    	                    {
    	                        if( xIt()->uModuleIdx == static_cast<u_int>(iModule) && (xIt()->uRegionsContribd & (1 << g_uCurRegion)) )
    	                        {
    	                            switch( xIt()->eReason )
    	                            {
    	                                case ContributingModule::REASON_VIS:
    	                                    uHighlightColour = 0xffffff00; // Yellow.
    	                                    break;

    	                                case ContributingModule::REASON_INTERSECTS:
    	                                    uHighlightColour = 0xffff8c0e; // Orange.
    	                                    break;

    	                                case ContributingModule::REASON_CLOSE:
    	                                    uHighlightColour = 0xffff8cee; // Pink.
    	                                    break;
    	                            }

    	                            break;
    	                        }
    	                    }
    	                }

    	                // If no other highlight, we need to highlight the current
    	                // region.
    	                if( !uHighlightColour && uRegionForThisModule == g_uCurRegion )
    	                {
    	                    uHighlightColour = 0xff00ff00; // Green.
    	                }
    	            }

    	            if( !uHighlightColour && (uRegionsToHighlight & (1 << uRegionForThisModule)) )
    	            {
    	                uHighlightColour = 0xff0000ff; // Blue.
    	            }

    	            // Any highlight?
    	            if( uHighlightColour )
    	            {
    	                Tools_Render::RenderMeshOutline( iModule, uHighlightColour );
    	            }
    	        }
    	        // End loop through modules.

    	        // Some additional information...
    	        {
    	            {
    	                u_int uNumRegionsReqd = 0;
    	                for( u_int u = ASURA_ENV_MAX_REGIONS; u--; )
    	                {
    	                    if( uRegionsToHighlight & (1 << u) ) { ++uNumRegionsReqd; }
    	                }

    	                Asura_Text_Output::Print( 1.0f,
    	                                          fY++,
    	                                          0xFFFFFFFF,
    	                                          0,
    	                                          "   %u region%s required for selected module.",
    	                                          uNumRegionsReqd,
    	                                          (1 == uNumRegionsReqd ? "" : "s") );
                    }

                    if( uRegionsToHighlight & (1 << g_uCurRegion) )
                    {
    	                u_int uNumModulesVisInRegionFromCur = 0;
    	                u_int uNumModulesVisInRegionFromInt = 0;

    	                for( int iOtherModule = 0; iOtherModule < Asura_Module_System::GetNumberOfModules(); ++iOtherModule )
    	                {
    	                    if( iOtherModule == iSelectedModule ) { continue; }

    	                    if( g_uCurRegion != pxEnv->GetModuleRegion(iOtherModule) ) { continue; }

    	                    if( Asura_Module_System::IsModuleLinked(iSelectedModule, iOtherModule) )
    	                    {
    	                        ++uNumModulesVisInRegionFromCur;
    	                    }
    	                    else
    	                    {
    	                        for( CLIF<ContributingModule*> xIt(&g_pxContributingModuleLists[iSelectedModule]); !xIt.done(); xIt.next() )
    	                        {
    	                            const ContributingModule* pxNext = xIt();

    	                            if( pxNext->uModuleIdx == static_cast<u_int>(iOtherModule) ) { continue; }
    	                        
    	                            if( !(pxNext->uRegionsContribd & (1 << g_uCurRegion)) ) { continue; }

    	                            if( ContributingModule::REASON_INTERSECTS != pxNext->eReason ) { continue; }

    	                            if( Asura_Module_System::IsModuleLinked(pxNext->uModuleIdx, iOtherModule) )
    	                            {
    	                                ++uNumModulesVisInRegionFromInt;
    	                                break;
    	                            }
    	                        }
    	                    }
    	                }

    	                Asura_Text_Output::Print( 1.0f,
    	                                          fY++,
    	                                          0xFFFFFFFF,
    	                                          0,
    	                                          "   %u module%s in cur region visible from cur module; %u module%s visible from intersecting modules.",
    	                                          uNumModulesVisInRegionFromCur,
    	                                          (1 == uNumModulesVisInRegionFromCur ? "" : "s"),
    	                                          uNumModulesVisInRegionFromInt,
    	                                          (1 == uNumModulesVisInRegionFromInt ? "" : "s") );
    	            }
                    else
                    {
                        ++fY;
                    }

    	            if( g_bOptionHliteModulesThatForcedCurRgn )
    	            {
    	                ++fY;

    	                Asura_Text_Output::Print( 1.0f, fY++, 0xffffffff, 0, "   Module highlighting:" );
    	                if( uRegionsToHighlight & (1 << g_uCurRegion) )
    	                {
    	                    Asura_Text_Output::Print( 1.0f, fY++, 0xffff0000, 0, "      Cur module" );
    	                    Asura_Text_Output::Print( 1.0f, fY++, 0xff00ff00, 0, "      Cur region" );
    	                    Asura_Text_Output::Print( 1.0f, fY++, 0xff0000ff, 0, "      Other req'd region" );                       
    	                    Asura_Text_Output::Print( 1.0f, fY++, 0xffffff00, 0, "      Module forcing cur region due to vis" );
    	                    Asura_Text_Output::Print( 1.0f, fY++, 0xffff8c0e, 0, "      Module forcing cur region due to intersection" );
    	                }
    	                else
    	                {
    	                    if( uRegionsToHighlight )
    	                    {
    	                        Asura_Text_Output::Print( 1.0f, fY++, 0xffffffff, 0, "      Selected module does not required current region." );
    	                    }
    	                    else
    	                    {
    	                        Asura_Text_Output::Print( 1.0f, fY++, 0xffffffff, 0, "      No regions required for selected module." );
    	                    }
    	                }
    	            }
    	        }
    	    }
    	    else // not if( 1 == pxSelectedModuleList->size() )
    	    {
    	        if( 0 == pxSelectedModuleList->size() )
    	        {
    	            Asura_Text_Output::Print( 1.0f, fY++, 0xffffffff, 0, "   Can't display; no modules selected." );
    	        }
    	        else
    	        {
    	            Asura_Text_Output::Print( 1.0f, fY++, 0xffffffff, 0, "   Can't display; more than one module selected." );
    	        }
    	    }
    	    // End else not if( 1 == pxSelectedModuleList->size() ).
    	}
    	// End if( g_bOptionHliteReqdForSelectedModule ).

    	if( g_bOptionHliteCursorRgn )
    	{
    	    // Which module is the cursor over?
    	    const int iHighlitModule = Tools_Module_Selection::GetHighlitModule();
    	    if( iHighlitModule >= 0 )
    	    {
    	        // Get region.
    	        const u_int uRegionCursorIsOver = pxEnv->GetModuleRegion( iHighlitModule );
    	        
    	        // Highlight all modules in it.
    	        for( int iModule = 0; iModule < pxEnv->m_iNumberOfModules; ++iModule )
    	        {
    	            if( pxEnv->GetModuleRegion(iModule) == uRegionCursorIsOver )
    	            {
    	                Tools_Render::RenderMeshOutline( iModule, 0xff00ff00 ); // Green.
    	            }
    	        }
    	    }
    	}

    	if( g_bOptionDispIgnoreFlag )
    	{
    	    for( int iModule = 0; iModule < pxEnv->m_iNumberOfModules; ++iModule )
    	    {
    	        if( pxEnv->GetModuleFlags(iModule) & ASURA_MODULE_FLAG_IGNORE_FOR_REGIONING )
    	        {
    	            Tools_Render::RenderMeshOutline( iModule, 0xff9fdf00 ); // Off green.
    	        }
    	    }
    	}
	}

    // General display stuff.

    Tools_Module_Selection::Render();

    //show information on module that the cursor is over
    {
        int iModuleIndex = Tools_Module_Selection::GetHighlitModule();
        if(iModuleIndex>=0)
        {
            const Asura_Mesh*               pxModuleMesh = Asura_Module_System::GetMeshForModule( iModuleIndex );
            const Asura_Environment_Module* pxEnvModule  = &pxEnv->Modules[iModuleIndex];

            float x,y;
            Asura_Cursor::GetScreenCoords(x,y);
            x++;
            y--;

            Asura_Text_Output::Print( x, y++, 0xff00ff00, 0, "%s", pxModuleMesh->Name.GetString() );
            Asura_Text_Output::Print( x, y++, 0xff00ff00, 0, "Region %u", pxEnvModule->m_uRegion );
        }        
    }

    // Show some stats about the current region.
    {
        Asura_Text_Output::Print( 1.0f, 4.0f, 0xFFFFFFFFu, 0, "Cur region: %u", g_uCurRegion );

        {
            const List<int>* pxModuleSelectionList = Tools_Module_Selection::GetSelectionList();
            if( pxModuleSelectionList->size() == 1 )
            {
                int iSelectedModule = pxModuleSelectionList->first_entry();
                const char* szName  = (Asura_Module_System::GetModuleData(iSelectedModule))->Mesh->Name.GetString();
                Asura_Text_Output::Print( 1.0f, 5.0f, 0xFFFFFFFFu, 0, "Cur module: %i (%s)", iSelectedModule, szName );
            }
            else
            {
                if( pxModuleSelectionList->size() )
                {
                    Asura_Text_Output::Print( 1.0f, 5.0f, 0xFFFFFFFFu, 0, "Cur module: (more than one selected)" );
                }
                else
                {
                    Asura_Text_Output::Print( 1.0f, 5.0f, 0xFFFFFFFFu, 0, "Cur module: (none selected)" );
                }
            }
            
            if( g_xBaseModuleList.size() == 1 )
            {
                int iBaseModule = g_xBaseModuleList.first_entry();
                const char* szName  = (Asura_Module_System::GetModuleData(iBaseModule))->Mesh->Name.GetString();
                Asura_Text_Output::Print( 1.0f, 6.0f, 0xFFFFFFFFu, 0, "Base module: %i (%s)", iBaseModule, szName );
            }
            else
            {
                if( g_xBaseModuleList.size() )
                {
                    Asura_Text_Output::Print( 1.0f, 6.0f, 0xFFFFFFFFu, 0, "Base module: %d selected",g_xBaseModuleList.size() );
                }
                else
                {
                    Asura_Text_Output::Print( 1.0f, 6.0f, 0xFFFFFFFFu, 0, "Base module: (none selected)" );
                }
            }
        }

        {
            u_int uNumModulesInThisRegion = 0;
            for( u_int uModule = pxEnv->m_iNumberOfModules; uModule--; )
            {
                if( pxEnv->GetModuleRegion(uModule) == g_uCurRegion )
                {
                    ++uNumModulesInThisRegion;
                }
            }
            Asura_Text_Output::Print( 1.0f, 7.0f, 0xffffffff, 0, "Num modules in this region: %u", uNumModulesInThisRegion );
        }

        Asura_Text_Output::Print( 1.0f, 8.0f, 0xffffffff, 0, "Num non-empty regions: %u", g_uNumNonEmptyRegions );

        if( g_xTopModules.size() )
        {
            Asura_Text_Output::Print( 1.0f, 9.0f, 0xffffffff, 0, "Max simultaneously req'd regions: %u", g_xTopModules.first_entry()->uNumRegionsReqd );
        }
    }
}

void Tools_Mode_Regions::PreSave( void )
{
    // Ensure required regions are up to date.
    CalculateRequiredRegions();
}

bool Tools_Mode_Regions::IsModified()
{
    return Tools_SaveAsura::IsMainFileModified() || Tools_SaveAsura::IsModuleLinkingModified();
}

void Tools_Mode_Regions::QuickSave()
{
	if(Tools_SaveAsura::IsModuleLinkingModified()) Tools_SaveAsura::SaveModuleLinking();
	Tools_SaveAsura::QuickSaveFile();
}


void Tools_Mode_Regions::HandleKeyboard()
{
    if(Asura_Overlay_System::OverlaysHaveKeyboardFocus()) return;
    if(Asura_UserInput::KeyPressed(ASURA_KEYID_ALT)) return;

    if(Asura_UserInput::KeyPressed(ASURA_KEYID_CTRL))
    {
		if(Asura_UserInput::DebouncedKeyPressed('z'))// 'ctrl+z' for  undo
        {
            Undo();
        }
        if(Asura_UserInput::DebouncedKeyPressed('y'))// 'ctrl+y' for  redo
        {												
            Redo();
        }
    }
    else if(Asura_UserInput::KeyPressed(ASURA_KEYID_SHIFT))
    {
        if(Asura_UserInput::DebouncedKeyPressed('f'))
        {
            if(IsDoingModuleLinking())
				RemoveModuleLinks();
        }
    }
    else
    {
        if(Asura_UserInput::DebouncedKeyPressed(ASURA_KEYID_TAB))
		{
			g_bOptionHliteModuleLinking	= !g_bOptionHliteModuleLinking;
			
			Tools_Timed_Message_System::AddMessage( 0xffffff, "Module linking %s.",g_bOptionHliteModuleLinking ? "on" : "off" );
		}
		
        if(Asura_UserInput::DebouncedKeyPressed('f'))// 'F' for apply region
        {
            if(IsDoingModuleLinking())
				AddModuleLinks();
			else
	            ApplyRegion();
        }
        if(Asura_UserInput::DebouncedKeyPressed('n'))
        {
            if( g_uCurRegion >0 ) { --g_uCurRegion; }
        }
        if(Asura_UserInput::DebouncedKeyPressed('m'))
        {
            if( g_uCurRegion < ASURA_ENV_MAX_REGIONS - 1 ) { ++g_uCurRegion; }
        }

        if( Asura_UserInput::DebouncedKeyPressed('r') )
        {
            SelectRegionForModule( -1 );
        }
        
        if( Asura_UserInput::DebouncedKeyPressed('t') )
        {
            int iModuleIdx = Tools_Module_Selection::GetHighlitModule();
            if( iModuleIdx >= 0 )
            {
                SelectRegionForModule( iModuleIdx );
            }
            else
            {
                Tools_Timed_Message_System::AddMessage( 0xffff00, "Can't switch to region of module under cursor: cursor isn't over a module." );
            }
        }
        
        if(Asura_UserInput::DebouncedKeyPressed('b'))
        {
            if(IsDoingModuleLinking())
			{
				SetBaseModule();
			}
        }
		if(Asura_UserInput::DebouncedKeyPressed(','))// '<' for  undo
        {
            Undo();
        }
        if(Asura_UserInput::DebouncedKeyPressed('.'))// '>' for  redo
        {												
            Redo();
        }



        if( g_bOptionHliteReqdForSelectedModule )
        {
            int iChange = 0;
            if( Asura_UserInput::DebouncedKeyPressed('j') )
            {
                iChange = -1;
            }
            else if( Asura_UserInput::DebouncedKeyPressed('k') )
            {
                iChange = 1;
            }

            if( iChange )
            {
                const List<int>* pxModuleSelectionList = Tools_Module_Selection::GetSelectionList();
                if( pxModuleSelectionList->size() == 1 )
                {
                    int iNewSelection = pxModuleSelectionList->first_entry();
                    while( true )
                    {
                        iNewSelection += iChange;

                        // Check we haven't gone out of range.
                        if( iNewSelection < 0 || iNewSelection >= Asura_Module_System::GetNumberOfModules() )
                        {
                            iNewSelection = -1;
                            break;
                        }

                        // Okay unless it's hidden.
                        if( !Tools_Render_Options::IsHidden(iNewSelection, 0) )
                        {
                            break;
                        }
                    }

                    if( -1 != iNewSelection )
                    {
                        Tools_Module_Selection::RequestSingleSelection( iNewSelection );
                    }
                }
                else
                {
                    if( pxModuleSelectionList->size() )
                    {
                        Tools_Timed_Message_System::AddMessage( 0xffff00, "Can't scroll through modules: more than one is selected." );
                    }
                    else
                    {
                        Tools_Timed_Message_System::AddMessage( 0xffff00, "Can't scroll through modules: none currently selected." );
                    }
                }
            }
            else // not if( iChange ) - ie. neither 'j' nor 'k' is down
            {
                if( Asura_Overlay_System::DoesOverlayExist(TOOLS_OVERLAY_REGIONS_TOPMODULES) )
                {
                    Asura_Assert( g_xTopModules.size(), "" );
                    Asura_Assert( 0 <= g_iPosInTopModuleList && g_iPosInTopModuleList < g_xTopModules.size(), "" );

                    if( Asura_UserInput::DebouncedKeyPressed('i') )
                    {
                        iChange = -1;
                    }
                    else if( Asura_UserInput::DebouncedKeyPressed('o') )
                    {
                        iChange = 1;
                    }

                    if( iChange )
                    {
                        int iNewPos = Asura_Maths::ClampToRange( g_iPosInTopModuleList + iChange, 0, g_xTopModules.size() - 1 );
                        if( iNewPos != g_iPosInTopModuleList )
                        {
                            g_iPosInTopModuleList = iNewPos;
                            Tools_Module_Selection::RequestSingleSelection( g_xTopModules[g_iPosInTopModuleList]->uModuleIdx );
                        }
                    }
                }
            }
            // End else not if( iChange ).
        }
        // End if( g_bOptionHliteReqdForSelectedModule ).
    }
}

void Tools_Mode_Regions::Undo( void )
{
    if(IsDoingModuleLinking())
	{
    	if( g_xModuleLinkUndo.Undo() )
    	{
    	    Tools_Timed_Message_System::AddMessage(0xffffff,"Undo (Module Links)");
    	}
	}
	else
	{
    	if( g_xUndo.Undo() )
    	{
    	    Tools_Timed_Message_System::AddMessage(0xffffff,"Undo (Regions)");
    	}
	}
}

void Tools_Mode_Regions::Redo( void )
{
    if(IsDoingModuleLinking())
	{
    	if( g_xModuleLinkUndo.Redo() )
    	{
    	    Tools_Timed_Message_System::AddMessage(0xffffff,"Redo (Module Links)");
    	}
	}
	else
	{
    	if( g_xUndo.Redo() )
    	{
    	    Tools_Timed_Message_System::AddMessage(0xffffff,"Redo (Regions)");
    	}
	}
}

bool Tools_Mode_Regions::IsModuleInHiddenRegion( int iModuleIdx )
{
    // In regions mode?
    if( !Tools_System::IsInRegionsMode() ) { return false; }

    // Get ptr to env data.
    const Asura_Environment* pxEnv = Asura_Environment_System::GetEnvironment();
    if( !pxEnv ) { return false; }

    // Check specified module index is valid.
    if( iModuleIdx < 0 || iModuleIdx > pxEnv->m_iNumberOfModules )
    {
        Asura_Assert( false, "Invalid module index." );
        return false;
    }

    // Okay, check whether this module's region is hidden or not.
    return !g_abDisplayRegion[pxEnv->GetModuleRegion(iModuleIdx)];
}

// F U N C T I O N   I M P L E M E N T A T I O N S ///////////////////////////

static void AddIgnoreFlagToSelectedModules( void )
{
    const List<int>* pxSelectedModuleList = Tools_Module_Selection::GetSelectionList();

    const u_int uNumModulesSelected = pxSelectedModuleList->size();
    if( uNumModulesSelected )
    {
        u_int uNumChanged = 0;

        Asura_Environment* pxEnv = Asura_Environment_System::GetEnvironment();
        for( CLIF<int> xIt(pxSelectedModuleList); !xIt.done(); xIt.next() )
        {
            if( !(pxEnv->GetModuleFlags(xIt()) & ASURA_MODULE_FLAG_IGNORE_FOR_REGIONING) )
            {
                pxEnv->AddModuleFlag( xIt(), ASURA_MODULE_FLAG_IGNORE_FOR_REGIONING );
                ++uNumChanged;

	            Tools_SaveAsura::SetModuleModified(xIt());

            }

        }

        if( uNumChanged )
        {
            g_bRegionAssignmentModified = true;
            if( uNumChanged < uNumModulesSelected )
            {
                Tools_Timed_Message_System::AddMessage( 0xffffff,
                                                        "Ignore flag applied to %u module(s) (%u selected modules already had it)",
                                                        uNumChanged,
                                                        uNumModulesSelected - uNumChanged );
            }
            else
            {
                Tools_Timed_Message_System::AddMessage( 0xffffff, "Ignore flag applied to %u module(s)", uNumChanged );
            }
        }
    }
    else
    {
        Tools_Timed_Message_System::AddMessage( 0xffff00, "Can't add flag: no modules selected." );
    }
}

static void AddToContributingModuleList( u_int uModule, u_int uContributingModule, u_int uRegionsContributed, ContributingModule::REASON eReason )
{
    // If this module has already contributed, update record.
    LIF<ContributingModule*> xIt( &g_pxContributingModuleLists[uModule] );
    for( ; !xIt.done(); xIt.next() )
    {
        if( xIt()->uModuleIdx == uContributingModule )
        {
            xIt()->uRegionsContribd = uRegionsContributed;
            xIt()->eReason          = eReason;

            break;
        }
    }

    // Add new entry to list if we didn't find it.
    if( xIt.done() )
    {
        ContributingModule* pxContributingModule = new ContributingModule;

        pxContributingModule->uModuleIdx       = uContributingModule;
        pxContributingModule->uRegionsContribd = uRegionsContributed;
        pxContributingModule->eReason          = eReason;

        g_pxContributingModuleLists[uModule].add_entry( pxContributingModule );
    }
}

static void ApplyRegion( void )
{
    const List<int>* pxSelectedList = Tools_Module_Selection::GetSelectionList();

    const u_int uNumModulesSelected = pxSelectedList->size();
    if( !uNumModulesSelected )
    {
        Tools_Timed_Message_System::AddMessage( 0xffffff, "Can't apply region: no modules selected." );
        return;
    }

    Asura_Environment* pxEnv = Asura_Environment_System::GetEnvironment();

    u_int uNumDifferent = 0;
    for( CLIF<int> xSelected(pxSelectedList); !xSelected.done(); xSelected.next() )
    {
        if( pxEnv->Modules[xSelected()].m_uRegion != g_uCurRegion )
        {
            g_xUndo.LogRegionChanged( xSelected() );
            pxEnv->Modules[xSelected()].m_uRegion = g_uCurRegion;
            ++uNumDifferent;

	        Tools_SaveAsura::SetModuleModified(xSelected());
        }
    }
    
    if( uNumDifferent )
    {
        g_bRegionAssignmentModified = true;

        if( uNumDifferent == uNumModulesSelected )
        {
            Tools_Timed_Message_System::AddMessage( 0xffffff, "Region %u applied to %u module(s)", g_uCurRegion, uNumDifferent );
        }
        else
        {
            Asura_Assert( uNumDifferent < uNumModulesSelected, "" );
            Tools_Timed_Message_System::AddMessage( 0xffffff,
                                                    "Region %u applied to %u module(s) (%u modules were already in it)",
                                                    g_uCurRegion,
                                                    uNumDifferent,
                                                    uNumModulesSelected - uNumDifferent );
        }
    }
}

static bool AreModulesOverlapping( const Asura_Bounding_Box& xBB1, const Asura_Bounding_Box& xBB2, float fBoundH, float fBoundV )
{
    u_int uOverlappingBits = 0;

    if( xBB1.MaxX < xBB2.MinX )
    {
        if( xBB2.MinX - xBB1.MaxX <= fBoundH ) { uOverlappingBits |= 0x1u; }
    }
    else if( xBB1.MinX > xBB2.MaxX )
    {
        if( xBB1.MinX - xBB2.MaxX <= fBoundH ) { uOverlappingBits |= 0x1u; }
    }
    else
    {
        uOverlappingBits |= 0x1u;
    }

    if( xBB1.MaxY < xBB2.MinY )
    {
        if( xBB2.MinY - xBB1.MaxY <= fBoundV ) { uOverlappingBits |= 0x2u; }
    }
    else if( xBB1.MinY > xBB2.MaxY )
    {
        if( xBB1.MinY - xBB2.MaxY <= fBoundV ) { uOverlappingBits |= 0x2u; }
    }
    else
    {
        uOverlappingBits |= 0x2u;
    }

    if( xBB1.MaxZ < xBB2.MinZ )
    {
        if( xBB2.MinZ - xBB1.MaxZ <= fBoundH ) { uOverlappingBits |= 0x4u; }
    }
    else if( xBB1.MinZ > xBB2.MaxZ )
    {
        if( xBB1.MinZ - xBB2.MaxZ <= fBoundH ) { uOverlappingBits |= 0x4u; }
    }
    else
    {
        uOverlappingBits |= 0x4u;
    }

    return( 0x7u == uOverlappingBits );
}

static bool CalculateRequiredRegions( void )
{
    // Determine whether or not we're currently in regions mode. If not,
    // don't update the stats, since we'll do it if/when we actually enter
    // regions mode.
    const bool bInRegionsMode = Tools_System::IsInRegionsMode();
    
    Asura_Environment* pxEnv = Asura_Environment_System::GetEnvironment();
    Asura_Assert( pxEnv, "" );

    const int iNumModules = pxEnv->m_iNumberOfModules;
    Asura_Assert( iNumModules >= 0, "" );
    if( iNumModules <= 0 )
    {
        return( 0 == iNumModules );
    }

    // Allocate an array for intermediate results from pass 1.
    u_int* puVisibleRegions = new u_int[iNumModules];
    if( !puVisibleRegions )
    {
        Tools_Timed_Message_System::AddMessage( 0xff0000, "Out of memory." );
        return false;
    }

    // Allocate array of lists to keep record of which modules caused which
    // regions to be required. If we've already allocated the array, empty it
    // again.
    // (MP - 25.10.2002) TODO: Need to worry about module count changing?
    if( bInRegionsMode )
    {
        if( g_pxContributingModuleLists )
        {
            for( u_int u = pxEnv->m_iNumberOfModules; u--; )
            {
                while( g_pxContributingModuleLists[u].size() )
                {
                    delete g_pxContributingModuleLists[u].first_entry();
                    g_pxContributingModuleLists[u].delete_first_entry();
                }
            }
        }
        else
        {
            g_pxContributingModuleLists = new List<ContributingModule*>[iNumModules];
            if( !g_pxContributingModuleLists )
            {
                Tools_Timed_Message_System::AddMessage( 0xff0000, "Out of memory." );
                delete[] puVisibleRegions;
                return false;
            }
        }
    }

	//Any module that has been flagged as scenery should automatically be treated as ignored for regioning as well
	//RAR 21/05/04
	const u_int uIGNORE_FOR_REGION_FLAGS = (ASURA_MODULE_FLAG_IGNORE_FOR_REGIONING|ASURA_MODULE_FLAG_SCENERY);

    // Pass 1:
    // Set bit r of puTempMask[m] iff any module in region r is visible from
    // module m. Note that we always want at least one bit set for every
    // module, ie. the bit corresponding to its own region.
    for( u_int uModule = iNumModules; uModule--; )
    {
        const u_int uRegionForThisModule = pxEnv->GetModuleRegion( uModule );

        u_int uVisRegions = (1 << uRegionForThisModule);

        // Modules with the ASURA_MODULE_FLAG_IGNORE_FOR_REGIONING flag only
        // get the region they're in.
        if( !(pxEnv->GetModuleFlags(uModule) & uIGNORE_FOR_REGION_FLAGS ) )
        {
            for( u_int uOtherModule = iNumModules; uOtherModule--; )
            {
                const u_int uRegionForOtherModule = pxEnv->GetModuleRegion( uOtherModule );

                // Is it in a different module and linked in?
                if( uRegionForOtherModule != uRegionForThisModule && Asura_Module_System::IsModuleLinked(uModule, uOtherModule) )
                {
                    uVisRegions |= (1 << uRegionForOtherModule);
                    if( bInRegionsMode )
                    {
                        AddToContributingModuleList( uModule, uOtherModule, (1 << uRegionForOtherModule), ContributingModule::REASON_VIS );
                    }
                }
            }
        }

        Asura_Assert( uVisRegions, "" );
        puVisibleRegions[uModule] = uVisRegions;
    }
    // End pass 1.

    // Pass 2:
    // For each module, also include the required regions of any intersecting
    // modules (provided they're linked in and not specially flagged) and
    // any modules that are nearby according to waypoints.
    // (MP - 19.02.2003) The check for intersection now includes a fudge
    // factor to account for the player's BB.
    {
#if USE_WAYPOINTS_FOR_REQD_REGION_DETERMINATION
        const bool bWaypointsAvailable = ( Asura_AI_Navigation_System::GetNumberOfWaypoints() > 0 );
        if( !bWaypointsAvailable )
        {
            Tools_Timed_Message_System::AddMessage( 0xff0000, "ERROR: You haven't generated waypoints for this level. These are required for dynamic loading to work properly." );
        }
#else
        const bool bWaypointsAvailable = false;
#endif

        for( u_int uModule = iNumModules; uModule--; )
        {
            const u_int uThisModuleVisRegions = puVisibleRegions[uModule];

            u_int uReqdRegions = uThisModuleVisRegions;

            // Modules with the ASURA_MODULE_FLAG_IGNORE_FOR_REGIONING flag
            // only get the region they're in.
            if( !(pxEnv->GetModuleFlags(uModule) & uIGNORE_FOR_REGION_FLAGS) )
            {
                Asura_Bounding_Box xBB;
                SetupWorldspaceModuleBB( uModule, &xBB );

                for( u_int uOtherModule = iNumModules; uOtherModule--; )
                {
                    // Check it has different vis regions from the module
                    // we're processing because otherwise it's not going to
                    // give us any new information and will just make it
                    // unclear later when we try and display the modules that
                    // forced a region to be required.
                    const u_int uOtherModuleVisRegions = puVisibleRegions[uOtherModule];
                    if( uOtherModuleVisRegions == uThisModuleVisRegions )
                    {
                        continue;
                    }

                    // Modules with the ASURA_MODULE_FLAG_IGNORE_FOR_REGIONING
                    // flag do not contribute additional regions to other
                    // modules.
                    if( pxEnv->GetModuleFlags(uOtherModule) & uIGNORE_FOR_REGION_FLAGS )
                    {
                        continue;
                    }

                    // Check for intersection. This check only applies if the
                    // other module is linked in.
                    if( Asura_Module_System::IsModuleLinked(uModule, uOtherModule) )
                    {
                        // (MP - 19.02.2003) TODO: We need to remove this
                        // hard-coded stuff and use some kind of project-
                        // specific GetBBForRegions() method here.

                        const float fBBSizeH = 0.6f;
                        const float fBBSizeV = Tools_System::This->GetDefaultPlayerHeight();

                        Asura_Bounding_Box xOtherBB;
                        SetupWorldspaceModuleBB( uOtherModule, &xOtherBB );

                        if( AreModulesOverlapping(xBB, xOtherBB, fBBSizeH, fBBSizeV) )
                        {
                            uReqdRegions |= uOtherModuleVisRegions;
                            if( bInRegionsMode )
                            {
                                AddToContributingModuleList( uModule, uOtherModule, uOtherModuleVisRegions, ContributingModule::REASON_INTERSECTS );
                            }
                            continue;
                        }
                    }

                    // Otherwise, check if this other module is close enough
                    // according to our navigation data for this module to
                    // require its regions.
                    if( bWaypointsAvailable )
                    {
                        if( IsModuleClose(uModule, uOtherModule) )
                        {
                            uReqdRegions |= uOtherModuleVisRegions;
                            if( bInRegionsMode) 
                            {
                                AddToContributingModuleList( uModule, uOtherModule, uOtherModuleVisRegions, ContributingModule::REASON_CLOSE );
                            }
                            continue;
                        }
                    }
                }
                // End loop through other modules.
            }
            // End if not module has ASURA_MODULE_FLAG_IGNORE_FOR_REGIONING set.

            pxEnv->Modules[uModule].m_uReqdRegions = uReqdRegions;
        }
        // End loop through modules.
    }
    // End pass 2.

    // Free temp arrays.
    delete[] puVisibleRegions;

    // Update stats.
    if( bInRegionsMode )
    {
        // Empty top module list.
        while( g_xTopModules.size() )
        {
            delete g_xTopModules.last_entry();
            g_xTopModules.delete_last_entry();
        }

        // Keep record of which regions are empty.
        bool abRegionIsEmpty[ASURA_ENV_MAX_REGIONS];
        for( u_int u = ASURA_ENV_MAX_REGIONS; u--; )
        {
            abRegionIsEmpty[u] = true;
        }

        for( u_int uModule = pxEnv->m_iNumberOfModules; uModule--; )
        {
            abRegionIsEmpty[pxEnv->GetModuleRegion(uModule)] = false;

            u_int uNumReqdRegionsForThisModule = 0;

            // Count required regions.
            {
                const u_int uReqdRegions = pxEnv->GetRequiredRegions( uModule );
                for( u_int u = ASURA_ENV_MAX_REGIONS; u--; )
                {
                    if( uReqdRegions & (1 <<u) )
                    {
                        ++uNumReqdRegionsForThisModule;
                    }
                }
            }

            // Insert into list?
            bool bInsert = true;
            Asura_Assert( g_xTopModules.size() <= MAX_MODULES_IN_TOP_MODULES_LIST, "" );
            if( g_xTopModules.size() == MAX_MODULES_IN_TOP_MODULES_LIST )
            {
                const TopModuleListEntry* pxLastEntry = g_xTopModules.last_entry();
                if( uNumReqdRegionsForThisModule <= pxLastEntry->uNumRegionsReqd )
                {
                    bInsert = false;
                }
            }

            if( bInsert )
            {
                TopModuleListEntry* pxEntry;
                if( g_xTopModules.size() < MAX_MODULES_IN_TOP_MODULES_LIST )
                {
                    // Haven't reached capacity yet so make new entry.
                    pxEntry = new TopModuleListEntry;
                }
                else
                {
                    // Overwrite last entry (and move it as necessary,
                    // obviously.)
                    pxEntry = g_xTopModules.last_entry();
                    g_xTopModules.delete_last_entry();
                }

                pxEntry->uModuleIdx      = uModule;
                pxEntry->uNumRegionsReqd = uNumReqdRegionsForThisModule;

                LIF<TopModuleListEntry*> xIt( &g_xTopModules );
                for( ; !xIt.done(); xIt.next() )
                {
                    if( xIt()->uNumRegionsReqd < uNumReqdRegionsForThisModule )
                    {
                        g_xTopModules.add_entry_before( pxEntry, xIt() );
                        break;
                    }
                }

                if( xIt.done() )
                {
                    g_xTopModules.add_entry_end( pxEntry );
                }
            }
            // End if( bInsert ).
        }
        // End loop through modules.

        // Count non-empty regions.
        g_uNumNonEmptyRegions = 0;
        for( u_int u = 0; u < ASURA_ENV_MAX_REGIONS; ++u )
        {
            if( !abRegionIsEmpty[u] )
            {
                g_auNonEmptyRegions[g_uNumNonEmptyRegions++] = u;
            }
        }
    }
    // End stats update.
        
    return true;
}

static void CreateRegionsOverlay( void )
{
    const float SMALL_GAP  = 1.2f;
    const float MEDIUM_GAP = 1.43f;
    const float BIG_GAP    = 2.17f;
    const float INSET      = 1.25f;

    if( !g_bRegionsOverlayOpen ) { return; }

    Asura_Overlay* pxOverlay;
    pxOverlay = new Asura_Overlay( "Regions mode", false, &g_xRegionsOverlayPosition );
    
    Asura_Dialog* pxDialog;
    
    Asura_Vector_2 xOffset( 1.0f, 3.0f );

    // Main functionality.
    {
        pxDialog = new Asura_StringDisplay_Dialog( "Main", xOffset );
        pxOverlay->AddDialog( pxDialog );

        xOffset.x += INSET;
        xOffset.y += MEDIUM_GAP;
        pxDialog = new Asura_Numerical_Dialog("Region", xOffset, (int*)&g_uCurRegion, 0, ASURA_ENV_MAX_REGIONS - 1, 1, "%d");
        pxOverlay->AddDialog(pxDialog);

        xOffset.y += MEDIUM_GAP;
        pxDialog = new Asura_Function_Dialog( "Apply region [F]", xOffset, ApplyRegion, NULL, NULL, true );
        pxOverlay->AddDialog(pxDialog);

        xOffset.y += MEDIUM_GAP;
        pxDialog = new Asura_Function_Dialog( "Switch to sel'd module's region [R]", xOffset, SelectRegionForModule, -1, NULL, NULL, true );
        pxOverlay->AddDialog( pxDialog );

        xOffset.x -= INSET;
    }

    // Flag functionality.
    {
        xOffset.y += BIG_GAP;
        pxDialog = new Asura_StringDisplay_Dialog( "Ignore-for-regioning flag", xOffset );
        pxOverlay->AddDialog( pxDialog );

        xOffset.x += INSET;
        xOffset.y += MEDIUM_GAP;
        pxDialog = new Asura_Function_Dialog( "Add to sel'd module(s)", xOffset, AddIgnoreFlagToSelectedModules, NULL, NULL, true );
        pxOverlay->AddDialog( pxDialog );

        xOffset.y += MEDIUM_GAP;
        pxDialog = new Asura_Function_Dialog( "Remove from sel'd module(s)", xOffset, RemoveIgnoreFlagFromSelectedModules, NULL, NULL, true );
        pxOverlay->AddDialog( pxDialog );

        xOffset.y += MEDIUM_GAP;
        pxDialog = new Asura_Tickbox_Dialog( "Show ignore flag", xOffset, &g_bOptionDispIgnoreFlag );
        pxOverlay->AddDialog( pxDialog );

        xOffset.x -= INSET;
    }

    // Highlight options.
    {
        xOffset.y += BIG_GAP;
        pxDialog = new Asura_StringDisplay_Dialog( "Highlight options", xOffset );
        pxOverlay->AddDialog( pxDialog );

        xOffset.x += INSET;
        xOffset.y += MEDIUM_GAP;
        pxDialog = new Asura_Tickbox_Dialog( "Hl all regions", xOffset, &g_bOptionHliteAllRgns );
        pxOverlay->AddDialog( pxDialog );

        xOffset.y += MEDIUM_GAP;
        pxDialog = new Asura_Tickbox_Dialog( "Hl current region", xOffset, &g_bOptionHliteCurRgn );
        pxOverlay->AddDialog( pxDialog );
    
        xOffset.y += MEDIUM_GAP;
        pxDialog = new Asura_Tickbox_Dialog( "Hl req'd regions for sel'd module", xOffset, &g_bOptionHliteReqdForSelectedModule );
        pxOverlay->AddDialog( pxDialog );

        xOffset.x += INSET;
        xOffset.y += SMALL_GAP;
        pxDialog = new Asura_Function_Dialog( "Open top modules dialog", xOffset, OpenTopModulesDialog, NULL, NULL, true );
        pxOverlay->AddDialog( pxDialog );
        g_pxOpenTopModulesDialog = pxDialog;

        xOffset.y += SMALL_GAP;
        pxDialog = new Asura_Tickbox_Dialog( "Hl modules that req'd cur region", xOffset, &g_bOptionHliteModulesThatForcedCurRgn );
        pxOverlay->AddDialog( pxDialog );
        g_pxHighlightModulesThatMadeReqdDialog = pxDialog;

        xOffset.x -= INSET;
        xOffset.y += MEDIUM_GAP;
        pxDialog = new Asura_Tickbox_Dialog( "Hl region cursor is over", xOffset, &g_bOptionHliteCursorRgn );
        pxOverlay->AddDialog( pxDialog );

        xOffset.y += MEDIUM_GAP;
        pxDialog = new Asura_Tickbox_Dialog( "Hl modules that req cur region", xOffset, &g_bOptionHliteModulesThatReqCurRgn );
        pxOverlay->AddDialog( pxDialog );

        xOffset.x -= INSET;
    }

    // Region display options.
    {
        xOffset.y += BIG_GAP;
        pxDialog = new Asura_StringDisplay_Dialog( "Region display options", xOffset );
        pxOverlay->AddDialog( pxDialog );

        xOffset.x += INSET;
        xOffset.y += MEDIUM_GAP;
        pxDialog = new Asura_Function_Dialog( "Show all regions", xOffset, ShowAllRegions, NULL, NULL, true );
        g_pxShowAllRegionsDialog = pxDialog;
        pxOverlay->AddDialog( pxDialog );

        xOffset.y += MEDIUM_GAP;
        pxDialog = new Asura_Function_Dialog( "Hide all regions", xOffset, HideAllRegions, NULL, NULL, true );
        g_pxHideAllRegionsDialog = pxDialog;
        pxOverlay->AddDialog( pxDialog );

        xOffset.y += MEDIUM_GAP;
        pxDialog = new Asura_Tickbox_Dialog( "Only show cur region", xOffset, &g_bOptionOnlyDispCurRegion );
        pxOverlay->AddDialog( pxDialog );

        xOffset.y += MEDIUM_GAP;
        pxDialog = new Asura_Function_Dialog( "Invert displayed regions", xOffset, InvertDisplayedRegions, NULL, NULL, true );
        g_pxInvertRegionsDialog = pxDialog;
        pxOverlay->AddDialog( pxDialog );

        xOffset.x -= INSET;
    }

	//Module linking
    {
        xOffset.y += BIG_GAP;
        pxDialog = new Asura_StringDisplay_Dialog( "Module linking options", xOffset );
        pxOverlay->AddDialog( pxDialog );

        xOffset.x += INSET;
        xOffset.y += MEDIUM_GAP;
        pxDialog = new Asura_Tickbox_Dialog( "Show module linking", xOffset, &g_bOptionHliteModuleLinking );
        pxOverlay->AddDialog( pxDialog );

        xOffset.y += MEDIUM_GAP;
        pxDialog = new Asura_Function_Dialog( "Set base module(s)", xOffset, SetBaseModule, NULL , IsDoingModuleLinking, true );
        pxOverlay->AddDialog( pxDialog );

        xOffset.y += MEDIUM_GAP;
        pxDialog = new Asura_Function_Dialog( "Add links", xOffset, AddModuleLinks, NULL , IsDoingModuleLinking, true  );
        pxOverlay->AddDialog( pxDialog );
     
        xOffset.y += MEDIUM_GAP;
        pxDialog = new Asura_Function_Dialog( "Remove links", xOffset, RemoveModuleLinks, NULL , IsDoingModuleLinking, true  );
        pxOverlay->AddDialog( pxDialog );

        xOffset.x -= INSET;
    }

    pxOverlay->m_uID = TOOLS_OVERLAY_MODE;

    Asura_Overlay_System::SubmitNewOverlay(pxOverlay,false);

    g_pxRegionSelectorOverlay = new RegionSelectorOverlay();
    Asura_Overlay_System::SubmitNewOverlay( g_pxRegionSelectorOverlay, false, false, false );
}

static bool GenerateOverlappingModuleLists( List<List<int>*>* pxOverlappingModuleLists, int iStartModule, List<int>* pxCurList )
{
    bool bFoundLongerList          = false;
    bool bRecursionFoundLongerList = false;

    for( int iModule = iStartModule; iModule < Asura_Module_System::GetNumberOfModules(); ++iModule )
    {
        // Does it overlap all modules in cur list?
        if( pxCurList )
        {
            Asura_Bounding_Box xBB;
            SetupWorldspaceModuleBB( iModule, &xBB );

            Asura_Bounding_Box xIntersectionOfBBs = xBB;

            bool bFoundNonOverlappingModule = false;

            // (MP - 19.02.2003) TODO: We need to remove this hard-coded
            // stuff and use some kind of project-specific GetBBForRegions()
            // method here.
            const float fBBSizeH = 0.6f;
            const float fBBSizeV = Tools_System::This->GetDefaultPlayerHeight();

            Asura_Bounding_Box xOtherBB;
            for( CLIF<int> xIt(pxCurList); !xIt.done(); xIt.next() )
            {
                SetupWorldspaceModuleBB( xIt(), &xOtherBB );

                if( !AreModulesOverlapping(xBB, xOtherBB, fBBSizeH, fBBSizeV) )
                {
                    bFoundNonOverlappingModule = true;
                    break;
                }

                xIntersectionOfBBs.MinX = Asura_Maths::Max( xIntersectionOfBBs.MinX, xOtherBB.MinX );
                xIntersectionOfBBs.MinY = Asura_Maths::Max( xIntersectionOfBBs.MinY, xOtherBB.MinY );
                xIntersectionOfBBs.MinZ = Asura_Maths::Max( xIntersectionOfBBs.MinZ, xOtherBB.MinZ );
                xIntersectionOfBBs.MaxX = Asura_Maths::Min( xIntersectionOfBBs.MaxX, xOtherBB.MaxX );
                xIntersectionOfBBs.MaxY = Asura_Maths::Min( xIntersectionOfBBs.MaxY, xOtherBB.MaxY );
                xIntersectionOfBBs.MaxZ = Asura_Maths::Min( xIntersectionOfBBs.MaxZ, xOtherBB.MaxZ );
            }

            if( bFoundNonOverlappingModule )
            {
                continue;
            }
#if USE_WAYPOINTS_FOR_REQD_REGION_DETERMINATION
            g_bFoundWaypointInBB = false;
            Asura_AI_Navigation_System::SearchWaypoints( xIntersectionOfBBs, WaypointCallback );
            if( !g_bFoundWaypointInBB )
            {
                continue;
            }
#endif 
        }

        // Okay, create new list.
        List<int>* pxNewList = new List<int>();
        if( pxCurList )
        {
            *pxNewList = *pxCurList;
        }
        pxNewList->add_entry_end( iModule );

        bFoundLongerList = true;

        // Recurse.
        if( GenerateOverlappingModuleLists(pxOverlappingModuleLists, iModule + 1, pxNewList) )
        {
            bRecursionFoundLongerList = true;
        }
    }

    if( pxCurList )
    {
        if( pxCurList->size() > 1 && !bRecursionFoundLongerList )
        {
            pxOverlappingModuleLists->add_entry( pxCurList );
        }
        else
        {
            delete pxCurList;
        }
    }

    return bFoundLongerList;
}

static const Asura_Bounding_Box& GetModuleBB( u_int uModule )
{
    return (Asura_Module_System::GetCollisionMeshForModule(uModule))->GetBoundingBox();
}

static void HideAllRegions( void )
{
    Asura_Assert( !g_bOptionOnlyDispCurRegion, "" );

    for( u_int u = ASURA_ENV_MAX_REGIONS; u--; )
    {
        g_abDisplayRegion[u] = false;
    }
}

static void InvertDisplayedRegions( void )
{
    if( !g_bOptionOnlyDispCurRegion )
    {
        for( u_int u = ASURA_ENV_MAX_REGIONS; u--; )
        {
            g_abDisplayRegion[u] = !g_abDisplayRegion[u];
        }
    }
}

static bool IsCurRegion( int iRegion )
{
    return( static_cast<u_int>(iRegion) == g_uCurRegion );
}

static bool IsModuleClose( u_int uModule, u_int uOtherModule )
{
    unsigned short usNumWaypoints = static_cast<u_short>(Asura_AI_Navigation_System::GetNumberOfWaypoints());
    Asura_Assert( usNumWaypoints > 0, "Shouldn't be calling IsModuleClose() if we don't have nav data." );

    const Asura_Module* pxModule      = Asura_Module_System::GetModuleData( uModule );
    const Asura_Module* pxOtherModule = Asura_Module_System::GetModuleData( uOtherModule );

    for( unsigned short usWP = 0; usWP < usNumWaypoints; ++usWP )
    {
        const Asura_AI_NavVertex* pxWP = Asura_AI_Navigation_System::GetWaypoint( usWP );
        if( IsWaypointInModule(pxWP, pxModule) )
        {
            for( unsigned short usOtherWP = 0; usOtherWP < usNumWaypoints; ++usOtherWP )
            {
                const Asura_AI_NavVertex* pxOtherWP = Asura_AI_Navigation_System::GetWaypoint( usOtherWP );
                if( IsWaypointInModule(pxOtherWP, pxOtherModule) )
                {
                    // Okay, can we reach this other WP from the first one?

                }
            }
        }
    }

    // (MP - 20.01.2003) TODO
    return false;
}

static bool IsRegionsOverlayOn( void )
{
    return g_bRegionsOverlayOpen;
}

static bool IsWaypointInModule( const Asura_AI_NavVertex* pxWP, const Asura_Module* pxModule )
{
    const Asura_Vector_3& xWPPos        = pxWP->m_xPosition;
    const Asura_Vector_3& xModulePos    = pxModule->Position;
    const Asura_Bounding_Box& xModuleBB = pxModule->Mesh->BoundingBox;

    // Check module isn't below WP.
    if( xModuleBB.MaxY + xModulePos.y > xWPPos.y ) { return false; }

    // Now that we know the module is in the WP's upper halfspace (as defined
    // by the x-z plane), just do a BS-BB test (in the module's objectspace.)
    return( xModuleBB.IntersectsSphere(xWPPos - xModulePos, pxWP->GetRadius()) );
}

static void OpenTopModulesDialog( void )
{
    // Nothing to do if the overlay is already open.
    if( Asura_Overlay_System::DoesOverlayExist(TOOLS_OVERLAY_REGIONS_TOPMODULES) )
    {
        return;
    }

    if( !g_xTopModules.size() )
    {
        // Should only happen if environment is empty!
        Asura_Assert( !Asura_Module_System::GetNumberOfModules(), "" );

        return;
    }

    // Reset position in top module list.
    g_iPosInTopModuleList = 0;

    Asura_Overlay* pxNewOverlay = new Asura_Overlay( "Top modules", false );
    pxNewOverlay->m_uID = TOOLS_OVERLAY_REGIONS_TOPMODULES;

    Asura_Dialog* pxNewDialog;

    Asura_Vector_2 xOffset( 1.0f, 3.0f );
    pxNewDialog = new Asura_Numerical_Dialog( "Position in top module list", xOffset, &g_iPosInTopModuleList, 0, g_xTopModules.size() - 1, 1, "%i" );
    pxNewOverlay->AddDialog( pxNewDialog );

    xOffset.y += 1.75f;
    pxNewDialog = new Asura_Function_Dialog( "Switch to this module", xOffset, SwitchToCurPosInTopModuleList );
    pxNewOverlay->AddDialog( pxNewDialog );

    Asura_Overlay_System::SubmitNewOverlay( pxNewOverlay );
}

static void RemoveIgnoreFlagFromSelectedModules( void )
{
    const List<int>* pxSelectedModuleList = Tools_Module_Selection::GetSelectionList();

    const u_int uNumModulesSelected = pxSelectedModuleList->size();
    if( uNumModulesSelected )
    {
        u_int uNumChanged = 0;

        Asura_Environment* pxEnv = Asura_Environment_System::GetEnvironment();
        for( CLIF<int> xIt(pxSelectedModuleList); !xIt.done(); xIt.next() )
        {
            if( pxEnv->GetModuleFlags(xIt()) & ASURA_MODULE_FLAG_IGNORE_FOR_REGIONING )
            {
                pxEnv->RemoveModuleFlag( xIt(), ASURA_MODULE_FLAG_IGNORE_FOR_REGIONING );
                ++uNumChanged;
	            Tools_SaveAsura::SetModuleModified(xIt());
            }
        }

        if( uNumChanged )
        {
            g_bRegionAssignmentModified = true;
            if( uNumChanged < uNumModulesSelected )
            {
                  Tools_Timed_Message_System::AddMessage( 0xffffff,
                                                          "Ignore flag removed from %u module(s) (%u selected modules didn't have it)",
                                                          uNumChanged,
                                                          uNumModulesSelected - uNumChanged );
            }
            else
            {
                  Tools_Timed_Message_System::AddMessage( 0xffffff, "Ignore flag removed from %d module(s)", uNumChanged );
            }
        }
    }
    else
    {
        Tools_Timed_Message_System::AddMessage( 0xffff00, "Can't remove flag: no modules selected." );
    }
}

static void SelectRegionForModule( int iModuleIdx )
{
    if( -1 == iModuleIdx )
    {
        const List<int>* pxSelectedModuleList = Tools_Module_Selection::GetSelectionList();
        if( 1 == pxSelectedModuleList->size() )
        {
            iModuleIdx = pxSelectedModuleList->first_entry();
        }
        else
        {
            if( pxSelectedModuleList->size() )
            {
                Tools_Timed_Message_System::AddMessage( 0xffff00, "Can't switch to selected module's region: more than one module is selected." );
            }
            else
            {
                Tools_Timed_Message_System::AddMessage( 0xffff00, "Can't switch to selected module's region: no modules currently selected." );
            }
            return;
        }
    }

    const Asura_Environment* pxEnv = Asura_Environment_System::GetEnvironment();
    Asura_Assert( pxEnv, "" );

    Asura_Assert( 0 <= iModuleIdx && iModuleIdx < pxEnv->m_iNumberOfModules, "" );
    g_uCurRegion = pxEnv->GetModuleRegion( iModuleIdx );
}

static void ShowAllRegions( void )
{
    Asura_Assert( !g_bOptionOnlyDispCurRegion, "" );

    for( u_int u = ASURA_ENV_MAX_REGIONS; u--; )
    {
        g_abDisplayRegion[u] = true;
    }
}

static void SetCurRegion( int iRegion )
{
    if( 0 <= iRegion && static_cast<u_int>(iRegion) < ASURA_ENV_MAX_REGIONS )
    {
        g_uCurRegion = static_cast<u_int>(iRegion);
    }
}

static void SetupWorldspaceModuleBB( u_int uModuleIdx, Asura_Bounding_Box* pxBB )
{
    *pxBB = GetModuleBB( uModuleIdx );
    pxBB->Displace( Asura_Module_System::GetPositionOfModule(uModuleIdx) );
}

static void SwitchToCurPosInTopModuleList( void )
{
    Asura_Assert( g_bOptionHliteReqdForSelectedModule, "" );
    Asura_Assert( Asura_Overlay_System::DoesOverlayExist(TOOLS_OVERLAY_REGIONS_TOPMODULES), "" );

    Asura_Assert( 0 <= g_iPosInTopModuleList && g_iPosInTopModuleList < g_xTopModules.size(), "" );
    Tools_Module_Selection::RequestSingleSelection( g_xTopModules[g_iPosInTopModuleList]->uModuleIdx );
}

static void ToggleRegionsOverlay( void )
{
    g_bRegionsOverlayOpen = !g_bRegionsOverlayOpen;
    if( g_bRegionsOverlayOpen )
    {
        CreateRegionsOverlay();
    }
    else
    {
        Asura_Overlay_System::CloseOverlays( TOOLS_OVERLAY_MODE );
    }
}

#if USE_WAYPOINTS_FOR_REQD_REGION_DETERMINATION
static bool WaypointCallback( u_short usWaypointIdx )
{
    g_bFoundWaypointInBB = true;
    return false;
}
#endif // USE_WAYPOINTS_FOR_REQD_REGION_DETERMINATION

static void SetBaseModule()
{
	//set the base module list to equal the selection list
	g_xBaseModuleList = *Tools_Module_Selection::GetSelectionList();

	if(g_xBaseModuleList.size()==0)
	{
		Tools_Timed_Message_System::AddMessage(0xffffff,"Base module list cleared");
	}
	else if(g_xBaseModuleList.size()==1)
	{
		const Asura_Mesh* pxModuleMesh = Asura_Module_System::GetMeshForModule(g_xBaseModuleList.first_entry());
		Tools_Timed_Message_System::AddMessage(0xffffff,"%s set as base module",pxModuleMesh->Name.GetString());
	}
	else
	{
		Tools_Timed_Message_System::AddMessage(0xffffff,"%d modules set as base module",g_xBaseModuleList.size());
	}
}

static void AddModuleLinks()
{
    const List<int>* pxSelectedModuleList = Tools_Module_Selection::GetSelectionList();
	if(g_xBaseModuleList.size()==0)
	{
		Tools_Timed_Message_System::AddMessage(0xffff00,"Can't add module links : no base module(s) set");
		return;
	}
	if(!pxSelectedModuleList->size())
	{
		Tools_Timed_Message_System::AddMessage(0xffff00,"Can't add module links : no modules selected");
		return;
	}

	//link all the selected modules to the base modules
	int iAddedCount = 0;
	for(CLIF<int> xSelectedLIF(pxSelectedModuleList);!xSelectedLIF.done();xSelectedLIF.next())
	{
		bool bLinkChanged = false;
		for(LIF<int> xBaseLIF(&g_xBaseModuleList);!xBaseLIF.done();xBaseLIF.next())
		{
			//don't add links to scenery modules
			if(Asura_Environment_System::GetEnvironment()->IsSceneryModule(xBaseLIF()))
				continue;


			if(!Asura_Module_System::IsModuleLinked(xBaseLIF(),xSelectedLIF()))
			{
				bLinkChanged = true;
				g_xModuleLinkUndo.LogLinkingChanged(xBaseLIF(),xSelectedLIF());
				Asura_Module_System::SetModuleLink(xBaseLIF(),xSelectedLIF(),0,true);
			}	
		}

		if(bLinkChanged)
		{
			iAddedCount++;
		}
	}

	Tools_Timed_Message_System::AddMessage(0xffffff,"%d module(s) linked",iAddedCount);

	if(iAddedCount)
	{
		//flag as modified
		Tools_SaveAsura::SetModuleLinkingModified();
		//will also have changed required regions ,so set as modified
        g_bRegionAssignmentModified = true;

	}
}

static void RemoveModuleLinks()
{
    const List<int>* pxSelectedModuleList = Tools_Module_Selection::GetSelectionList();
	if(g_xBaseModuleList.size()==0)
	{
		Tools_Timed_Message_System::AddMessage(0xffff00,"Can't remove module links : no base module(s) set");
		return;
	}
	if(!pxSelectedModuleList->size())
	{
		Tools_Timed_Message_System::AddMessage(0xffff00,"Can't remove module links : no modules selected");
		return;
	}

	//unlink all the selected modules from the base modules
	int iRemovedCount = 0;
	for(CLIF<int> xSelectedLIF(pxSelectedModuleList);!xSelectedLIF.done();xSelectedLIF.next())
	{
		bool bLinkChanged = false;
		for(LIF<int> xBaseLIF(&g_xBaseModuleList);!xBaseLIF.done();xBaseLIF.next())
		{
			if(Asura_Module_System::IsModuleLinked(xBaseLIF(),xSelectedLIF()))
			{
				bLinkChanged = true;
				g_xModuleLinkUndo.LogLinkingChanged(xBaseLIF(),xSelectedLIF());
				Asura_Module_System::SetModuleLink(xBaseLIF(),xSelectedLIF(),0,false);
			}	
		}

		if(bLinkChanged)
		{
			iRemovedCount++;
		}
	}

	Tools_Timed_Message_System::AddMessage(0xffffff,"%d module(s) unlinked",iRemovedCount);
	
	if(iRemovedCount)
	{
		//flag as modified
		Tools_SaveAsura::SetModuleLinkingModified();
		//will also have changed required regions ,so set as modified
        g_bRegionAssignmentModified = true;
	}
}

static bool IsDoingModuleLinking()
{
	return g_bOptionHliteModuleLinking;
}
