/*==LICENSE==*

CyanWorlds.com Engine - MMOG client, server and tools
Copyright (C) 2011  Cyan Worlds, Inc.

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.

You can contact Cyan Worlds, Inc. by email legal@cyan.com
 or by snail mail at:
      Cyan Worlds, Inc.
      14617 N Newport Hwy
      Mead, WA   99021

*==LICENSE==*/
#pragma warning(disable: 4284)
#include "HeadSpin.h"
#include "hsTypes.h"
#include "hsWindowHndl.h"
#include "plClient.h"
#include "hsStream.h"
#include "plResMgr/plResManager.h"
#include "plResMgr/plKeyFinder.h"
#include "pnKeyedObject/plKey.h"
#include "pnKeyedObject/plFixedKey.h"
#include "pnMessage/plRefMsg.h"
#include "pnSceneObject/plSceneObject.h"
#include "pnSceneObject/plCoordinateInterface.h"
#include "plScene/plSceneNode.h"
#include "pnMessage/plTimeMsg.h"
#include "pnMessage/plClientMsg.h"
#include "pfCamera/plVirtualCamNeu.h"
#include "hsTimer.h"
#include "plPipeline/hsG3DDeviceSelector.h"
#include "plFile/plEncryptedStream.h"
#include "plFile/plFileUtils.h"
#include "plInputCore/plInputManager.h"
#include "plInputCore/plInputInterfaceMgr.h"
#include "plInputCore/plInputDevice.h"
#include "plPhysX/plSimulationMgr.h"
#include "plNetClient/plNetClientMgr.h"
#include "plAvatar/plAvatarMgr.h"
#include "plScene/plRelevanceMgr.h"
#include "pnTimer/plTimerCallbackManager.h"
#include "pfAudio/plListener.h"
#include "pnMessage/plCmdIfaceModMsg.h"
#include "plMessage/plRoomLoadNotifyMsg.h"
#include "pnMessage/plPlayerPageMsg.h"
#include "pnMessage/plCameraMsg.h"
#include "plMessage/plTransitionMsg.h"
#include "plMessage/plLinkToAgeMsg.h"
#include "plMessage/plPreloaderMsg.h"
#include "plMessage/plNetCommMsgs.h"
#include "plMessage/plAgeLoadedMsg.h"

#include "pfConsoleCore/pfConsoleEngine.h"
#include "pfConsole/pfConsole.h"
#include "pfConsole/pfConsoleDirSrc.h"
#include "plScene/plPageTreeMgr.h"
#include "plScene/plVisMgr.h"
#include "plFile/hsFiles.h"

#include "pfKI/pfKI.h"

#include "plAudio/plAudioSystem.h"
#include "plAudio/plAudioCaps.h"

#include "plStatGather/plProfileManagerFull.h"

#include "plPipeline.h"
#include "plPipeline/plPipelineCreate.h"
#include "plPipeline/plPipeDebugFlags.h"
#include "plPipeline/plTransitionMgr.h"
#include "plPipeline/plCaptureRender.h"
#include "plPipeline/plDynamicEnvMap.h"
#include "plNetClient/plLinkEffectsMgr.h"
#include "plAvatar/plAvatarClothing.h"
#include "plAvatar/plArmatureMod.h"
#include "pnMessage/plProxyDrawMsg.h"

#include "plScene/plRenderRequest.h"
#include "plDrawable/plAccessGeometry.h"
#include "plPipeResReq.h"
#include "plDrawable/plVisLOSMgr.h"

#include "plGImage/plBitmap.h"

#include "plStatusLog/plStatusLog.h"
#include "plProgressMgr/plProgressMgr.h"
#include "plPipeline/plDTProgressMgr.h"
#include "plPipeline/plBinkPlayer.h"
#include "plMessage/plMovieMsg.h"

#include "plSDL/plSDL.h"

#include "pnDispatch/plDispatch.h"
#include "pnDispatch/plDispatchLogBase.h"
#include "pfGameGUIMgr/pfGameGUIMgr.h"
#include "pfPython/cyMisc.h"
#include "plMessage/plInputEventMsg.h"
#include "plMessage/plRenderRequestMsg.h"
#include "pnMessage/plEventCallbackMsg.h"
#include "plModifier/plSimpleModifier.h"
#include "plAudible.h"
#include "plMessage/plAnimCmdMsg.h"
#include "pnMessage/plSoundMsg.h"
#include "pnMessage/plAudioSysMsg.h"
#include "plMessage/plRenderMsg.h"
#include "plAgeLoader/plResPatcher.h"
#include "pfPython/cyPythonInterface.h"
#include "plUnifiedTime/plClientUnifiedTime.h"
#include "pfAnimation/plAnimDebugList.h"
#include "pfGameGUIMgr/pfGUICtrlGenerator.h"

#include "plGImage/plWinFontCache.h"
#include "plGImage/plFontCache.h"

#include "pfJournalBook/pfJournalBook.h"

#include "plAvatar/plAGAnimInstance.h"
#include "plAgeLoader/plAgeLoader.h"
#include "plClientKey/plClientKey.h"

#include "plQuality.h"
#include "plGLight/plShadowCaster.h"

#include "plNetClient/plNetLinkingMgr.h"
#include "plNetCommon/plNetCommonConstants.h"
#include "plNetGameLib/plNetGameLib.h"

#include "pfSecurePreloader/pfSecurePreloader.h"
#include "pfLocalizationMgr/pfLocalizationMgr.h"

#include "pfCsrSrv/pfCsrSrv.h"

#include "plTweak.h"

#define MSG_LOADING_BAR

// static hsVector3 gAbsDown(0,0,-hsScalar1);

static plDispatchBase* gDisp = nil;
static plTimerCallbackManager* gTimerMgr = nil;
static plAudioSystem* gAudio = nil;

hsBool plClient::fDelayMS = false;

plClient* plClient::fInstance=nil;

static hsTArray<HMODULE>        fLoadedDLLs;

plClient::plClient()
: fPipeline(nil),
    fDone(false),
    fQuitIntro(false),
    fWindowHndl(nil),
    fInputManager(nil),
    fConsole(nil),
    fCurrentNode(nil),
    fNewCamera(nil),
    fpAuxInitDir(nil),
    fTransitionMgr(nil),
    fLinkEffectsMgr(nil),
    fProgressBar(nil),
    fGameGUIMgr(nil),
    fKIGUIGlue(nil),
    fWindowActive(false),
    fAnimDebugList(nil),
    fClampCap(-1),
    fQuality(0),
    fPageMgr(nil),
    fFontCache(nil),
    fHoldLoadRequests(false),
    fNumLoadingRooms(0),
    fNumPostLoadMsgs(0),
    fPostLoadMsgInc(0.f),
    fPatchGlobalAges(false)
{
#ifndef PLASMA_EXTERNAL_RELEASE
    bPythonDebugConnected = false;
#endif

    hsStatusMessage("Constructing client\n");
    plClient::SetInstance(this);
    // gNextRoom[0] = '\0';

    // Setup the timer. These can be overriden with console commands.
    hsTimer::SetRealTime(true);
#ifdef HS_DEBUGGING
//  hsTimer::SetRealTime(false);
    hsTimer::SetTimeClamp(0.1f);
#else // HS_DEBUGGING
//  hsTimer::SetRealTime(true);
    hsTimer::SetTimeClamp(0);
#endif // HS_DEBUGGING
    
    IDetectAudioVideoSettings();        // need to do this before the console is created

    /// allow console commands to start working early
    // Create the console engine
    fConsoleEngine = TRACKED_NEW pfConsoleEngine();
    
    // create network mgr before console runs
    plNetClientMgr::SetInstance(TRACKED_NEW plNetClientMgr);
    plAgeLoader::SetInstance(TRACKED_NEW plAgeLoader);

    // Use it to parse the init directory
    wchar initFolder[MAX_PATH];
    PathGetInitDirectory(initFolder, arrsize(initFolder));
    pfConsoleDirSrc     dirSrc( fConsoleEngine, initFolder, L"*.ini" );
    
#ifndef PLASMA_EXTERNAL_RELEASE
    // internal builds also parse the local init folder
    dirSrc.ParseDirectory( L"init", L"*.ini" );
#endif

    /// End of console stuff
}

plClient::~plClient()
{
    hsStatusMessage("Destructing client\n");

    plClient::SetInstance( nil );

    delete fPageMgr;
    delete [] fpAuxInitDir;
}

#include "plGImage/plAVIWriter.h"
#include "pfCharacter/pfMarkerMgr.h"

hsBool plClient::Shutdown()
{
    plSynchEnabler ps(false);   // disable dirty state tracking during shutdown 
    delete fProgressBar;

    CsrSrvShutdown();

    // Just in case, clear this out (trying to fix a crash bug where this is still active at shutdown)
    plDispatch::SetMsgRecieveCallback(nil);

    // Let the resmanager know we're going to be shutting down.
    hsgResMgr::ResMgr()->BeginShutdown();

    // Must kill off all movies before shutting down audio.
    IKillMovies();

    plgAudioSys::Activate(false);
    plBinkPlayer::DeInit();
    //
    // Get any proxies to commit suicide.
    plProxyDrawMsg* nuke = TRACKED_NEW plProxyDrawMsg(plProxyDrawMsg::kAllTypes
                                            | plProxyDrawMsg::kDestroy);
    plgDispatch::MsgSend(nuke);

    if (plAVIWriter::IsInitialized())
        plAVIWriter::Instance().Shutdown();

    hsStatusMessage( "Shutting down client...\n" );

    // First, before anybody else goes away, write out our key mappings
    if( plInputInterfaceMgr::GetInstance() )
        plInputInterfaceMgr::GetInstance()->WriteKeyMap();

    // tell Python that its ok to shutdown
    PythonInterface::WeAreInShutdown(); 

    // Shutdown the journalBook API
    pfJournalBook::SingletonShutdown();

    /// Take down the KI
    pfGameGUIMgr    *mgr = pfGameGUIMgr::GetInstance();
    if( mgr )
        mgr->UnloadDialog( "KIBlackBar" );  // unload the blackbar which will bootstrap in the rest of the KI dialogs

    // Take down our GUI control generator
    pfGUICtrlGenerator::Instance().Shutdown();

    if (plNetClientMgr::GetInstance())
    {   
        plNetClientMgr::GetInstance()->Shutdown();
        plNetClientMgr::GetInstance()->UnRegisterAs(kNetClientMgr_KEY);     // deletes NetClientMgr instance
        plNetClientMgr::SetInstance(nil);
    }
    
    if (plAgeLoader::GetInstance())
    {   
        plAgeLoader::GetInstance()->Shutdown();
        plAgeLoader::GetInstance()->UnRegisterAs(kAgeLoader_KEY);           // deletes instance
        plAgeLoader::SetInstance(nil);
    }
    
    if (pfSecurePreloader::IsInstanced())
    {
        pfSecurePreloader::GetInstance()->Shutdown();
        // pfSecurePreloader handles its own fixed key unregistration
    }

    if (fInputManager)
    {
        fInputManager->UnRegisterAs(kInput_KEY);
        fInputManager = nil;
    }

    if( fGameGUIMgr != nil )
    {
        fGameGUIMgr->UnRegisterAs( kGameGUIMgr_KEY );
        fGameGUIMgr = nil;
    }

    for (int i = 0; i < fRooms.Count(); i++)
    {
        plSceneNode *sn = fRooms[i].fNode;
        GetKey()->Release(sn->GetKey());
    }
    fRooms.Reset();
    fRoomsLoading.clear();

    // Shutdown plNetClientMgr

    plAccessGeometry::DeInit();

    delete fPipeline;
    fPipeline = nil;

    if (plSimulationMgr::GetInstance())
        plSimulationMgr::Shutdown();
    plAvatarMgr::ShutDown();
    plRelevanceMgr::DeInit();
    
    if (fPageMgr)
        fPageMgr->Reset();

    if( fKIGUIGlue != nil )
    {
        fKIGUIGlue->UnRegisterAs( kKIGUIGlue_KEY );
        fKIGUIGlue = nil;
    }

    if( fTransitionMgr != nil )
    {
        fTransitionMgr->UnRegisterAs( kTransitionMgr_KEY );
        fTransitionMgr = nil;
    }
    
    delete fConsoleEngine;
    fConsoleEngine = nil;

    if (fLinkEffectsMgr)
    {
        fLinkEffectsMgr->UnRegisterAs( kLinkEffectsMgr_KEY);
        fLinkEffectsMgr=nil;
    }

    plClothingMgr::DeInit();

    if( fFontCache != nil )
    {
        fFontCache->UnRegisterAs( kFontCache_KEY );
        fFontCache = nil;
    }

    pfMarkerMgr::Shutdown();

    delete fAnimDebugList;

//#ifndef PLASMA_EXTERNAL_RELEASE
    if( fConsole != nil )
    {
        // UnRegisterAs destroys the object for us
        fConsole->UnRegisterAs( kConsoleObject_KEY );
        fConsole = nil;
    }
//#endif

    PythonInterface::finiPython();
    
    if (fNewCamera)
        fNewCamera->UnRegisterAs( kVirtualCamera1_KEY );

    // mark the listener for death.
    // there's no need to keep this around...
    plUoid lu(kListenerMod_KEY);
    plKey pLKey = hsgResMgr::ResMgr()->FindKey(lu);
    if (pLKey)
    {   
        plListener* pLMod = plListener::ConvertNoRef(pLKey->GetObjectPtr());
        if (pLMod)
            pLMod->UnRegisterAs(kListenerMod_KEY);
    }

    plgAudioSys::Shutdown();

    if (pfLocalizationMgr::InstanceValid())
        pfLocalizationMgr::Shutdown();
    
    ShutdownDLLs();

    plVisLOSMgr::DeInit();

    delete fPageMgr;
    fPageMgr = nil;
    plGlobalVisMgr::DeInit();

#ifdef TRACK_AG_ALLOCS
    DumpAGAllocs();
#endif // TRACK_AG_ALLOCS

    // This will destruct the client. Do it last.
    UnRegisterAs(kClient_KEY);
    

    return false;
}

void plClient::InitAuxInits()
{
    // Use another init directory specified in Command line Arg -i
    if (fpAuxInitDir)
        pfConsoleDirSrc     dirSrc( fConsoleEngine, fpAuxInitDir, "*.ini" );
}

void plClient::InitInputs()
{
    hsStatusMessage("InitInputs client\n");
    fInputManager = TRACKED_NEW plInputManager( fWindowHndl );
    fInputManager->CreateInterfaceMod(fPipeline);
    fInputManager->RegisterAs( kInput_KEY );
    plgDispatch::Dispatch()->RegisterForExactType(plIMouseXEventMsg::Index(), fInputManager->GetKey());
    plgDispatch::Dispatch()->RegisterForExactType(plIMouseYEventMsg::Index(), fInputManager->GetKey());
    plgDispatch::Dispatch()->RegisterForExactType(plIMouseBEventMsg::Index(), fInputManager->GetKey());
    plgDispatch::Dispatch()->RegisterForExactType(plEvalMsg::Index(), fInputManager->GetKey());
    plInputDevice* pKeyboard = TRACKED_NEW plKeyboardDevice();
    fInputManager->AddInputDevice(pKeyboard);
    
    plInputDevice* pMouse = TRACKED_NEW plMouseDevice();
    fInputManager->AddInputDevice(pMouse);

    if( fWindowActive )
        fInputManager->Activate( true );
}

void plClient::ISetGraphicsDefaults()
{
    // couldn't find display mode set defaults write to ini file
    wchar graphicsIniFile[MAX_PATH];
    PathGetInitDirectory(graphicsIniFile, arrsize(graphicsIniFile));
    PathAddFilename(graphicsIniFile, graphicsIniFile, L"graphics.ini", arrsize(graphicsIniFile));
    IWriteDefaultGraphicsSettings(graphicsIniFile);
    plPipeline::fInitialPipeParams.Windowed = plPipeline::fDefaultPipeParams.Windowed;
    plPipeline::fInitialPipeParams.AntiAliasingAmount = plPipeline::fDefaultPipeParams.AntiAliasingAmount;
    plPipeline::fInitialPipeParams.AnisotropicLevel = plPipeline::fDefaultPipeParams.AnisotropicLevel;
    plPipeline::fInitialPipeParams.TextureQuality = plPipeline::fDefaultPipeParams.TextureQuality;
    plPipeline::fInitialPipeParams.VSync = plPipeline::fDefaultPipeParams.VSync;
    plShadowCaster::EnableShadowCast(plPipeline::fDefaultPipeParams.Shadows ? true : false);
    plQuality::SetQuality(plPipeline::fDefaultPipeParams.VideoQuality);
    if( (fClampCap >= 0) && (fClampCap < plQuality::GetCapability()) )
        plQuality::SetCapability(fClampCap);
    plDynamicCamMap::SetEnabled(plPipeline::fDefaultPipeParams.PlanarReflections ? true : false);
}

hsBool plClient::InitPipeline()
{
    hsStatusMessage("InitPipeline client\n");
    HWND hWnd = fWindowHndl;
    
    hsG3DDeviceModeRecord dmr;
    hsG3DDeviceSelector devSel;
    devSel.Enumerate(hWnd);
    devSel.RemoveUnusableDevModes(true);

    if (!devSel.GetDefault(&dmr))
    {
        hsMessageBox("No suitable rendering devices found.","Plasma", hsMessageBoxNormal, hsMessageBoxIconError);
        return true;
    }

    hsG3DDeviceRecord *rec = (hsG3DDeviceRecord *)dmr.GetDevice();
    int res = -1;

    if(!plPipeline::fInitialPipeParams.Windowed)
    {
        // find our resolution if we're not in windowed mode
        for ( int i = 0; i < rec->GetModes().GetCount(); i++ )
        {
            hsG3DDeviceMode *mode = rec->GetMode(i);
            if ((mode->GetWidth() == plPipeline::fInitialPipeParams.Width) &&
                (mode->GetHeight() == plPipeline::fInitialPipeParams.Height) &&
                (mode->GetColorDepth() == plPipeline::fInitialPipeParams.ColorDepth))
            {
                res = i;
                break;
            }
        }
        if(res != -1)
        {
            // found it set it as the current mode.
            dmr = hsG3DDeviceModeRecord(*rec, *rec->GetMode(res));
        }
        else
        {
            ISetGraphicsDefaults();
        }
    }

    if(plPipeline::fInitialPipeParams.TextureQuality == -1)
    {
        plPipeline::fInitialPipeParams.TextureQuality = dmr.GetDevice()->GetCap(hsG3DDeviceSelector::kCapsPixelShader) ? 2 : 1;
    }
    else
    {
        // clamp value to range
        if(plPipeline::fInitialPipeParams.TextureQuality > 2) plPipeline::fInitialPipeParams.TextureQuality = 2;
        if(plPipeline::fInitialPipeParams.TextureQuality < 0) plPipeline::fInitialPipeParams.TextureQuality = 0;
        plBitmap::SetGlobalLevelChopCount(2 - plPipeline::fInitialPipeParams.TextureQuality);
    }

    plPipeline *pipe = plPipelineCreate::CreatePipeline( hWnd, &dmr );
    if( pipe->GetErrorString() != nil )
    {
        ISetGraphicsDefaults();
#ifdef PLASMA_EXTERNAL_RELEASE
        hsMessageBox("There was an error initializing the video card.\nSetting defaults.", "Error", hsMessageBoxNormal);
#else
        hsMessageBox( pipe->GetErrorString(), "Error creating pipeline", hsMessageBoxNormal );
#endif
        delete pipe;
        devSel.GetDefault(&dmr);
        pipe = plPipelineCreate::CreatePipeline( hWnd, &dmr );
        if(pipe->GetErrorString() != nil)
        {
            // not much else we can do
            return true;
        }
    }
    fPipeline = pipe;

    hsVector3 up;
    hsPoint3 from, at;
    from.Set(0, 0, 10.f);
    at.Set(0, 20.f, 10.f);
    up.Set(0,0,-1.f);
    hsMatrix44 cam;
    cam.MakeCamera(&from,&at,&up);

    float   yon = 500.0f;

    pipe->SetFOV( 60.f, hsIntToScalar( 60.f * pipe->Height() / pipe->Width() ) );
    pipe->SetDepth( 0.3f, yon );

    hsMatrix44 id;
    id.Reset();

    pipe->SetWorldToCamera( cam, id );
    pipe->RefreshMatrices();

    // Do this so we're still black before we show progress bars, but the correct color coming out of 'em
    fClearColor.Set( 0.f, 0.f, 0.f, 1.f );
    pipe->SetClear(&fClearColor);
    pipe->ClearRenderTarget();

    plAccessGeometry::Init(pipe);

    if( fPipeline )
        fPipeline->LoadResources();

    return false;
}

//============================================================================
void    plClient::SetClearColor( hsColorRGBA &color )
{
    fClearColor = color;
    if( fPipeline != nil )
    {
        fPipeline->SetClear(&fClearColor, nil);
    }
}

//============================================================================
void plClient::IDispatchMsgReceiveCallback()
{
    if (fInstance->fProgressBar)
        fInstance->fProgressBar->Increment(1);

    static char buf[30];
    sprintf(buf, "Msg %d", fInstance->fNumPostLoadMsgs);
    fInstance->IIncProgress(fInstance->fPostLoadMsgInc, buf);

    fInstance->fNumPostLoadMsgs++;
}


//============================================================================
hsBool plClient::MsgReceive(plMessage* msg)
{
    if (plGenRefMsg * genRefMsg = plGenRefMsg::ConvertNoRef(msg)) {
        // do nothing, we just use the client's key to ref vault image nodes.
        return true;
    }
    
    plClientRefMsg* pRefMsg = plClientRefMsg::ConvertNoRef(msg);
    if (pRefMsg)
    {
        switch(pRefMsg->fType)
        {
        case plClientRefMsg::kLoadRoom :
            #ifndef PLASMA_EXTERNAL_RELEASE
            plStatusLog::AddLineS( "pageouts.log", ".. ClientRefMsg received for room %s", pRefMsg->GetRef() != nil ? pRefMsg->GetRef()->GetKey()->GetUoid().GetObjectName() : "nilref" );
            #endif

            // was it that the room was loaded?
            if (hsCheckBits(pRefMsg->GetContext(), plRefMsg::kOnCreate))
                IRoomLoaded(plSceneNode::Convert(pRefMsg->GetRef()), false);
            // or was it that the room was unloaded?
            else if (hsCheckBits(pRefMsg->GetContext(), plRefMsg::kOnDestroy))
                IRoomUnloaded(plSceneNode::Convert(pRefMsg->GetRef()));
            #ifndef PLASMA_EXTERNAL_RELEASE
            else
                plStatusLog::AddLineS("pageouts.log", "..    refMsg is UNHANDLED");
            #endif
            break;

        case plClientRefMsg::kLoadRoomHold:
            if (hsCheckBits(pRefMsg->GetContext(), plRefMsg::kOnCreate))
                IRoomLoaded(plSceneNode::Convert(pRefMsg->GetRef()), true);
            break;

            //
            // Manually add room.
            // Add to pageMgr, but don't load the entire room.
            //
        case plClientRefMsg::kManualRoom:
            {
                if (pRefMsg->GetContext() & plRefMsg::kOnCreate ||
                    pRefMsg->GetContext() & plRefMsg::kOnRequest)
                {
                    hsBool found=false;
                    plSceneNode *pNode = plSceneNode::ConvertNoRef(pRefMsg->GetRef()); 
                    int i;
                    for (i = 0; i < fRooms.Count(); i++)
                    {
                        if (fRooms[i].fNode->GetKey() == pRefMsg->GetSender())
                        {
                            found=true;
                            break;
                        }
                    }
                    if (!found)
                    {                   
                        if (pNode)
                        {
                            fRooms.Append( plRoomRec( pNode, 0 ) );
                            fPageMgr->AddNode(pNode);
                        }
                    }
                }
                else
                {
                    plSceneNode* node = plSceneNode::ConvertNoRef(pRefMsg->GetRef());
                    if(node)
                    {
                        int i;
                        for (i = 0; i < fRooms.Count(); i++)
                        {
                            if (fRooms[i].fNode->GetKey() == node->GetKey())
                            {
                                fRooms.Remove(i);
                                break;
                            }
                        }
                        fPageMgr->RemoveNode(node);
                    }
                }
            }
            break;
            
        }
    }

    plClientMsg* pMsg = plClientMsg::ConvertNoRef(msg);
    if (pMsg)
    {
        switch(pMsg->GetClientMsgFlag())
        {
        case plClientMsg::kQuit:
            SetDone(true);
            break;

        case plClientMsg::kLoadRoom:
        case plClientMsg::kLoadRoomHold:
            {
                IQueueRoomLoad(pMsg->GetRoomLocs(), (pMsg->GetClientMsgFlag() == plClientMsg::kLoadRoomHold));
                if (!fHoldLoadRequests)
                    ILoadNextRoom();
            }
            break;

        case plClientMsg::kUnloadRoom:
            IUnloadRooms(pMsg->GetRoomLocs());
            break;

        case plClientMsg::kLoadNextRoom:
            ILoadNextRoom();
            break;

        // Load optimizations: messages to pre-load and un-load all the keys in a given age
        case plClientMsg::kLoadAgeKeys:
            {
                plResManager *mgr = (plResManager *)hsgResMgr::ResMgr();
                mgr->LoadAgeKeys( pMsg->GetAgeName() );
            }
            break;

        case plClientMsg::kReleaseAgeKeys:
            {
                plResManager *mgr = (plResManager *)hsgResMgr::ResMgr();
                mgr->DropAgeKeys( pMsg->GetAgeName() );
            }
            break;
            
        case plClientMsg::kDisableRenderScene:
            {
                plClient::GetInstance()->SetFlag( plClient::kFlagDBGDisableRender, true );
            }
            break;
        case plClientMsg::kEnableRenderScene:
            {
                plClient::GetInstance()->SetFlag( plClient::kFlagDBGDisableRender, false );
            }
            break;

        case plClientMsg::kResetGraphicsDevice:
            {
                ResetDisplayDevice(pMsg->fGraphicsSettings.fWidth, pMsg->fGraphicsSettings.fHeight, pMsg->fGraphicsSettings.fColorDepth, pMsg->fGraphicsSettings.fWindowed, pMsg->fGraphicsSettings.fNumAASamples, pMsg->fGraphicsSettings.fMaxAnisoSamples, pMsg->fGraphicsSettings.fVSync);
            }
            break;

        case plClientMsg::kSetGraphicsDefaults:
            {
                ISetGraphicsDefaults();
                ResetDisplayDevice(plPipeline::fDefaultPipeParams.Width, plPipeline::fDefaultPipeParams.Height, plPipeline::fDefaultPipeParams.ColorDepth, plPipeline::fDefaultPipeParams.Windowed,
                    plPipeline::fDefaultPipeParams.AntiAliasingAmount, plPipeline::fDefaultPipeParams.AnisotropicLevel, plPipeline::fDefaultPipeParams.VSync, true);
            }
            break;

        }
        return true;
    }
    plRenderRequestMsg* rendReq = plRenderRequestMsg::ConvertNoRef(msg);
    if( rendReq )
    {
        IAddRenderRequest(rendReq->Request());
        return true;
    }
    plEventCallbackMsg* callback = plEventCallbackMsg::ConvertNoRef(msg);
    if( callback )
    {
        char str[256];
        sprintf(str, "Callback event from %s\n", callback->GetSender()
                    ? callback->GetSender()->GetName()
                    : "Unknown");
        hsStatusMessage(str);
        static int gotten = 0;
        if( ++gotten > 5 )
        {
            plSimpleModifier* simpMod = plSimpleModifier::ConvertNoRef(callback->GetSender()->ObjectIsLoaded());
            plAudible* aud = plAudible::ConvertNoRef(callback->GetSender()->ObjectIsLoaded());
            if( simpMod )
            {
                plAnimCmdMsg* cmd = TRACKED_NEW plAnimCmdMsg;
                cmd->AddReceiver(simpMod->GetKey());
                cmd->SetCmd(plAnimCmdMsg::kRemoveCallbacks);
                cmd->AddCallback(callback);
                plgDispatch::MsgSend(cmd);
                hsRefCnt_SafeUnRef(callback);
            }
            else if( aud )
            {
                plSoundMsg* cmd = TRACKED_NEW plSoundMsg;
                cmd->AddReceiver(aud->GetKey());
                cmd->SetCmd(plSoundMsg::kRemoveCallbacks);
                cmd->AddCallback(callback);
                plgDispatch::MsgSend(cmd);
                hsRefCnt_SafeUnRef(callback);
            }
            hsStatusMessage("Removed\n");
            gotten = 0;
        }
        return true;
    }
    plMovieMsg* mov = plMovieMsg::ConvertNoRef(msg);
    if( mov )
    {
        return IHandleMovieMsg(mov);
    }

    plLinkEffectsTriggerMsg* linkFX = plLinkEffectsTriggerMsg::ConvertNoRef(msg);
    if (linkFX)
    {
        if (!linkFX->IsLeavingAge())
        {
#ifdef MSG_LOADING_BAR
            // Temporary stat gathering stuff
            #if 0//ndef PLASMA_EXTERNAL_RELEASE
            hsUNIXStream s;
            s.Open("Messages.txt", "at");
            static bool firstLog = true;
            if (firstLog)
            {
                firstLog = false;
                s.WriteString("------------------------------------\n");
            }
            char buf[256];
            sprintf(buf, "%s %d\n", plAgeLoader::GetInstance()->GetCurrAgeFilename(), fNumPostLoadMsgs);
            s.WriteString(buf);
            s.Close();
            #endif
#endif
        }
        return true;
    }

    //============================================================================
    // plNetCommAuthMsg
    //============================================================================
    if (plNetCommAuthMsg * authCommMsg = plNetCommAuthMsg::ConvertNoRef(msg)) {
        IHandleNetCommAuthMsg(authCommMsg);
        return true;
    }

    //============================================================================
    // plPreloaderMsg
    //============================================================================
    if (plPreloaderMsg * preloaderMsg = plPreloaderMsg::ConvertNoRef(msg)) {
        IHandlePreloaderMsg(preloaderMsg);
        return true;
    }

    return hsKeyedObject::MsgReceive(msg);
}

//============================================================================
hsBool plClient::IHandleMovieMsg(plMovieMsg* mov)
{
    if( !(mov->GetFileName() && *mov->GetFileName()) )
        return true;

    int i;
    i = fMovies.GetCount();
    if( !(mov->GetCmd() & plMovieMsg::kMake) )
    {
        for( i = 0; i < fMovies.GetCount(); i++ )
        {
            if( !stricmp(mov->GetFileName(), fMovies[i]->GetFileName()) )
                break;
        }
    }
    if( i == fMovies.GetCount() )
    {

        fMovies.Append(TRACKED_NEW plBinkPlayer);
        fMovies[i]->SetFileName(mov->GetFileName());
    }

    if( mov->GetCmd() & plMovieMsg::kAddCallbacks )
    {
        int j;
        for( j = 0; j < mov->GetNumCallbacks(); j++ )
            fMovies[i]->AddCallback(mov->GetCallback(j));
    }
    if( mov->GetCmd() & plMovieMsg::kMove )
        fMovies[i]->SetPosition(mov->GetCenter());
    if( mov->GetCmd() & plMovieMsg::kScale )
        fMovies[i]->SetScale(mov->GetScale());
    if( mov->GetCmd() & plMovieMsg::kColorAndOpacity )
        fMovies[i]->SetColor(mov->GetColor());
    if( mov->GetCmd() & plMovieMsg::kColor )
    {
        hsColorRGBA c = fMovies[i]->GetColor();
        c.Set(mov->GetColor().r, mov->GetColor().g, mov->GetColor().b, c.a);
        fMovies[i]->SetColor(c);
    }
    if( mov->GetCmd() & plMovieMsg::kOpacity )
    {
        hsColorRGBA c = fMovies[i]->GetColor();
        c.a = mov->GetColor().a;
        fMovies[i]->SetColor(c);
    }
    if( mov->GetCmd() & plMovieMsg::kFadeIn )
    {
        fMovies[i]->SetFadeFromColor(mov->GetFadeInColor());
        fMovies[i]->SetFadeFromTime(mov->GetFadeInSecs());
    }
    if( mov->GetCmd() & plMovieMsg::kFadeOut )
    {
        fMovies[i]->SetFadeToColor(mov->GetFadeOutColor());
        fMovies[i]->SetFadeToTime(mov->GetFadeOutSecs());
    }
    if( mov->GetCmd() & plMovieMsg::kVolume )
        fMovies[i]->SetVolume(mov->GetVolume());

    if( mov->GetCmd() & plMovieMsg::kStart )
        fMovies[i]->Start(fPipeline, fWindowHndl);
    if( mov->GetCmd() & plMovieMsg::kPause )
        fMovies[i]->Pause(true);
    if( mov->GetCmd() & plMovieMsg::kResume )
        fMovies[i]->Pause(false);
    if( mov->GetCmd() & plMovieMsg::kStop )
        fMovies[i]->Stop();

    // If a movie has lost its filename, it means something went horribly wrong
    // with playing it and it has shutdown. Or we just stopped it. Either way, 
    // we need to clear it out of our list.
    if( !(fMovies[i]->GetFileName() && *fMovies[i]->GetFileName()) )
    {
        delete fMovies[i];
        fMovies.Remove(i);
    }

    return true;
}

int plClient::IFindRoomByLoc(const plLocation& loc)
{
    for (int i = 0; i < fRooms.Count(); i++)
    {
        if (fRooms[i].fNode->GetKey()->GetUoid().GetLocation() == loc)
            return i;
    }

    return -1;
}

bool plClient::IIsRoomLoading(const plLocation& loc)
{
    for (int i = 0; i < fRoomsLoading.size(); i++)
    {
        if (fRoomsLoading[i] == loc)
            return true;
    }
    return false;
}

void plClient::SetHoldLoadRequests(bool hold)
{
    fHoldLoadRequests = hold;
    if (!fHoldLoadRequests)
        ILoadNextRoom();
}

#include "plResMgr/plPageInfo.h"

void plClient::IQueueRoomLoad(const std::vector<plLocation>& locs, bool hold)
{
    bool allSameAge = true;
    const char* lastAgeName = nil;

    UInt32 numRooms = 0;
    for (int i = 0; i < locs.size(); i++)
    {
        const plLocation& loc = locs[i];

        const plPageInfo* info = plKeyFinder::Instance().GetLocationInfo(loc);
        bool alreadyLoaded = (IFindRoomByLoc(loc) != -1);
        bool isLoading = IIsRoomLoading(loc);
        if (!info || alreadyLoaded || isLoading)
        {
            #ifdef HS_DEBUGGING
            if (!info)
                hsStatusMessageF("Ignoring LoadRoom request for location 0x%x because we can't find the location", loc.GetSequenceNumber());
            else if (alreadyLoaded)
                hsStatusMessageF("Ignoring LoadRoom request for %s-%s, since room is already loaded", info->GetAge(), info->GetPage());
            else if (isLoading)
                hsStatusMessageF("Ignoring LoadRoom request for %s-%s, since room is currently loading", info->GetAge(), info->GetPage());
            #endif

            continue;
        }

        fLoadRooms.push_back(TRACKED_NEW LoadRequest(loc, hold));

        if (!lastAgeName || hsStrEQ(info->GetAge(), lastAgeName))
            lastAgeName = info->GetAge();
        else
            allSameAge = false;

//      hsStatusMessageF("+++ Loading room %s-%s", info.GetAge(), info.GetPage());
        numRooms++;
    }

    if (numRooms == 0)
        return;

    fNumLoadingRooms += numRooms;
}

void plClient::ILoadNextRoom()
{
    LoadRequest* req = nil;

    while (!fLoadRooms.empty())
    {
        req = fLoadRooms.front();
        fLoadRooms.pop_front();

        bool alreadyLoaded = (IFindRoomByLoc(req->loc) != -1);
        hsBool isLoading = IIsRoomLoading(req->loc);
        if (alreadyLoaded || isLoading)
        {
            delete req;
            req = nil;
            fNumLoadingRooms--;
        }
        else
            break;
    }

    if (req)
    {
        plClientRefMsg* pRefMsg = TRACKED_NEW plClientRefMsg(GetKey(),
            plRefMsg::kOnCreate, -1,
            req->hold ? plClientRefMsg::kLoadRoomHold : plClientRefMsg::kLoadRoom);

        fRoomsLoading.push_back(req->loc); // flag the location as currently loading

        // PageInPage is not guaranteed to finish synchronously, just FYI
        plResManager *mgr = (plResManager *)hsgResMgr::ResMgr();
        mgr->PageInRoom(req->loc, plSceneNode::Index(), pRefMsg);

        delete req;

        plClientMsg* nextRoom = TRACKED_NEW plClientMsg(plClientMsg::kLoadNextRoom);
        nextRoom->Send(GetKey());
    }
}

void plClient::IUnloadRooms(const std::vector<plLocation>& locs)
{
    for (int i = 0; i < locs.size(); i++)
    {
        const plLocation& loc = locs[i];

        if (!loc.IsValid())
            continue;

        plKey nodeKey = nil;

        // First, look in our room list. It *should* be there, which allows us to avoid a
        // potential nasty reload-find in the resMgr.
        int roomIdx = IFindRoomByLoc(loc);
        if (roomIdx != -1)
            nodeKey = fRooms[roomIdx].fNode->GetKey();

        if (nodeKey == nil)
        {
            nodeKey = plKeyFinder::Instance().FindSceneNodeKey(loc);
        }

        if (nodeKey != nil)
        {
            plSceneNode* node = plSceneNode::ConvertNoRef(nodeKey->ObjectIsLoaded());
            if (node)
            {
                #ifndef PLASMA_EXTERNAL_RELEASE
                plStatusLog::AddLineS("pageouts.log", "SceneNode for %s loaded; Removing node", node->GetKey()->GetUoid().GetObjectName());
                #endif
                fPageMgr->RemoveNode(node);
            }
            else
            {
                #ifndef PLASMA_EXTERNAL_RELEASE
                plStatusLog::AddLineS("pageouts.log", "SceneNode for %s NOT loaded", nodeKey->GetUoid().GetObjectName());
                #endif
            }
            GetKey()->Release(nodeKey);     // release notify interest in scene node
        
            UInt32 recFlags = 0;
            if (roomIdx != -1)
            {
                recFlags = fRooms[roomIdx].fFlags;
                fRooms.Remove(roomIdx);
            }

            if (node == fCurrentNode)
                fCurrentNode = nil;

            #ifndef PLASMA_EXTERNAL_RELEASE
            plStatusLog::AddLineS("pageouts.log", "Telling netClientMgr about paging out %s", nodeKey->GetUoid().GetObjectName());
            #endif

            if (plNetClientMgr::GetInstance() != nil)
            {
                // Don't care really about the message that just came in, we care whether it was really held or not
                if (!hsCheckBits(recFlags, plRoomRec::kHeld))
                    plAgeLoader::GetInstance()->StartPagingOutRoom(&nodeKey, 1);
                // Tell NetClientManager not to expect any pageout info on this guy, since he was held
                else
                    plAgeLoader::GetInstance()->IgnorePagingOutRoom(&nodeKey, 1);
            }
        }
        else
        {
            #ifndef PLASMA_EXTERNAL_RELEASE
//          plStatusLog::AddLineS("pageouts.log", "++ Can't find node key for paging out room %s, loc 0x%x",
//              pMsg->GetRoomName() != nil ? pMsg->GetRoomName() : "",
//              loc.GetSequenceNumber());
            #endif
        }
    }
}

void plClient::IRoomLoaded(plSceneNode* node, bool hold)
{
    fCurrentNode = node; 
    // make sure we don't already have this room in the list:
    hsBool bAppend = true;
    for (int i = 0; i < fRooms.Count(); i++)
    {
        if (fRooms[i].fNode == fCurrentNode)
        {   
            bAppend = false;
            break;
        }
    }
    if (bAppend)
    {
        if (hold)
        {
            fRooms.Append(plRoomRec(fCurrentNode, plRoomRec::kHeld));
        }
        else
        {
            fRooms.Append(plRoomRec(fCurrentNode, 0));
            fPageMgr->AddNode(fCurrentNode);
        }
    }

    fNumLoadingRooms--;
    
    // Shut down the progress bar if that was the last room
    if (fProgressBar != nil && fNumLoadingRooms <= 0)
    {
#ifdef MSG_LOADING_BAR
        if (!hold)
        {
            struct AgeMsgCount { const char* AgeName; int NumMsgs; };
            static AgeMsgCount ageMsgCount[] =
            {
                { "BahroCave",          2600    },
                { "BaronCityOffice",    670     },
                { "city",               269000  },
                { "Cleft",              11000   },
                { "Garden",             19700   },
                { "Garrison",           28800   },
                { "Gira",               3300    },
                { "Kadish",             19700   },
                { "Neighborhood",       19900   },
                { "Nexus",              1400    },
                { "Personal",           20300   },
                { "Teledahn",           48000   }
            };

            char name[256];
            strcpy(name, &fProgressBar->GetTitle()[strlen("Loading ")]);
            name[strlen(name)-3] = '\0';

            // Get the precalculated value for how many messages will be
            // sent out before the screen actually fades in
            int numMsgs = 0;
            for (int i = 0; i < sizeof(ageMsgCount)/sizeof(AgeMsgCount); i++)
            {
                if (hsStrEQ(ageMsgCount[i].AgeName, name))
                {
                    numMsgs = ageMsgCount[i].NumMsgs;
                    break;
                }
            }

            fNumPostLoadMsgs = 0;

            // The last 10% of the age loading bar is for messages, so adjust
            // our progress bar increment to fill the bar fully when all
            // messages have been sent
            float max = fProgressBar->GetMax();
            float amtLeft = max - (max * 0.9f);
            fPostLoadMsgInc = (numMsgs != 0) ? amtLeft / numMsgs : 0;
            
#ifndef PLASMA_EXTERNAL_RELEASE
            if (plDispatchLogBase::IsLogging())
                plDispatchLogBase::GetInstance()->LogStatusBarChange(fProgressBar->GetTitle(), "displaying messages");
#endif // PLASMA_EXTERNAL_RELEASE
#endif
        }
    }

    hsRefCnt_SafeUnRef(fCurrentNode);
    plKey pRmKey = fCurrentNode->GetKey();
    plAgeLoader::GetInstance()->FinishedPagingInRoom(&pRmKey, 1);
    // *** this used to call "ActivateNode" (in physics) which wasn't implemented.
    // *** we should make this "turn on" physics for the selected node
    // *** depending on what guarantees we can make about the load state -- anything useful?

    // now tell all those who are interested that a room was loaded
    if (!hold)
    {
        plRoomLoadNotifyMsg* loadmsg = TRACKED_NEW plRoomLoadNotifyMsg;
        loadmsg->SetRoom(pRmKey);
        loadmsg->SetWhatHappen(plRoomLoadNotifyMsg::kLoaded);
        plgDispatch::MsgSend(loadmsg);
    }
    else
        hsStatusMessageF("Done loading hold room %s, t=%f\n", pRmKey->GetName(), hsTimer::GetSeconds());

    plLocation loc = pRmKey->GetUoid().GetLocation();
    for (int i = 0; i < fRoomsLoading.size(); i++)
    {
        if (fRoomsLoading[i] == loc)
        {
            fRoomsLoading.erase(fRoomsLoading.begin() + i);
            break;
        }
    }
    
    if (!fNumLoadingRooms)
        IStopProgress();
}

//============================================================================
void plClient::IRoomUnloaded(plSceneNode* node)
{
    #ifndef PLASMA_EXTERNAL_RELEASE
    plStatusLog::AddLineS("pageouts.log", "..    refMsg is onDestroy");
    #endif

    fCurrentNode = node; 
    hsRefCnt_SafeUnRef(fCurrentNode);
    plKey pRmKey = fCurrentNode->GetKey();
    if (plAgeLoader::GetInstance())
        plAgeLoader::GetInstance()->FinishedPagingOutRoom(&pRmKey, 1);

    // tell all those who are interested that a room was unloaded
    plRoomLoadNotifyMsg* loadmsg = TRACKED_NEW plRoomLoadNotifyMsg;
    loadmsg->SetRoom(pRmKey);
    loadmsg->SetWhatHappen(plRoomLoadNotifyMsg::kUnloaded);
    plgDispatch::MsgSend(loadmsg);
}

void plClient::IReadKeyedObjCallback(plKey key)
{
    fInstance->IIncProgress(1, key->GetName());
}

//============================================================================
void plClient::IProgressMgrCallbackProc(plOperationProgress * progress)
{
    if(!fInstance)
        return;

    fInstance->fMessagePumpProc();
    fInstance->IDraw();
}

//============================================================================
void plClient::IIncProgress (hsScalar byHowMuch, const char * text)
{
    if (fProgressBar) {
#ifndef PLASMA_EXTERNAL_RELEASE
        fProgressBar->SetStatusText( text );
#endif
        fProgressBar->Increment( byHowMuch );
    }
}

//============================================================================
void    plClient::IStartProgress( const char *title, hsScalar len )
{
    if (fProgressBar)
    {
        fProgressBar->SetLength(fProgressBar->GetMax()+len);
    }
    else
    {
        fProgressBar = plProgressMgr::GetInstance()->RegisterOperation(len, title, plProgressMgr::kNone, false, true);
#ifndef PLASMA_EXTERNAL_RELEASE
        if (plDispatchLogBase::IsLogging())
            plDispatchLogBase::GetInstance()->LogStatusBarChange(fProgressBar->GetTitle(), "starting");
#endif // PLASMA_EXTERNAL_RELEASE

        ((plResManager*)hsgResMgr::ResMgr())->SetProgressBarProc(IReadKeyedObjCallback);
        plDispatch::SetMsgRecieveCallback(IDispatchMsgReceiveCallback);

        fLastProgressUpdate = 0.f;
    }
    // Workaround for NVidia driver bug, showing up as BCO not there first time.
    // See Mantis bug 0014590.
    if( fPipeline )
        fPipeline->LoadResources();
}


//============================================================================
void    plClient::IStopProgress( void )
{
    if (fProgressBar)
    {
#ifndef PLASMA_EXTERNAL_RELEASE
        if (plDispatchLogBase::IsLogging())
            plDispatchLogBase::GetInstance()->LogStatusBarChange(fProgressBar->GetTitle(), "done");
#endif // PLASMA_EXTERNAL_RELEASE

        plDispatch::SetMsgRecieveCallback(nil);
        ((plResManager*)hsgResMgr::ResMgr())->SetProgressBarProc(IReadKeyedObjCallback);
        delete fProgressBar;
        fProgressBar = nil;

        plPipeResReq::Request();

        fFlags.SetBit(kFlagGlobalDataLoaded);       
        if (fFlags.IsBitSet(kFlagAsyncInitComplete))
            ICompleteInit();
    }
}

/*****************************************************************************
*
*   
*
***/

extern  hsBool  gDataServerLocal;

#include "plQuality.h"
#include "plLoadMask.h"

#if 0
class LoginNetClientCommCallback : public plNetClientComm::Callback
{
public:
    enum Op {kAuth, kCreatePlayer, kGetPlayerList, kLeave, kDeletePlayer};

    LoginNetClientCommCallback() : plNetClientComm::Callback(), fNumCurrentOps(0)
    {}

    virtual void OperationStarted( UInt32 context )
    {
        fNumCurrentOps++;
    }
    virtual void OperationComplete( UInt32 context, int resultCode )
    {
        if (context == kAuth)
        {
            if (hsSucceeded(resultCode))
            {
                plClient::GetInstance()->fAuthPassed = true;
            }
        }
        else if (context == kGetPlayerList)
        {
            if ( hsSucceeded( resultCode ) )
            {
                UInt32 numPlayers = fCbArgs.GetInt(0);
                UInt32 pId = -1;
                std::string pName;

                for (UInt32 i = 0; i < numPlayers; i++)
                {
                    pId = fCbArgs.GetInt((UInt16)(i*3+1));
                    pName = fCbArgs.GetString((UInt16)(i*3+2));

                    if (pName == plClient::GetInstance()->fUsername)
                    {
                        plClient::GetInstance()->fPlayerID = pId;
                        break;
                    }
                }
            }
        }
        else if (context == kCreatePlayer)
        {
            if (hsSucceeded(resultCode))
                plClient::GetInstance()->fPlayerID = fCbArgs.GetInt(0);
        }
        else if (context == kDeletePlayer)
        {
            if (hsSucceeded(resultCode))
                plClient::GetInstance()->fPlayerID = -1;
        }

        fNumCurrentOps--;
    }

    bool IsActive()
    {
        return fNumCurrentOps > 0;
    }

private:
    int fNumCurrentOps;
};
#endif

//============================================================================
hsBool plClient::StartInit()
{
    hsStatusMessage("Init client\n");
    fFlags.SetBit( kFlagIniting );

    pfLocalizationMgr::Initialize("dat");

    plQuality::SetQuality(fQuality);
    if( (GetClampCap() >= 0) && (GetClampCap() < plQuality::GetCapability()) )
        plQuality::SetCapability(GetClampCap());

    /// 2.16.2001 mcn - Moved console engine init to constructor, 
    /// so we could use console commands even before the pipeline init

    plDTProgressMgr::DeclareThyself();

    // Set our callback for the progress manager so everybody else can use it
    fLastProgressUpdate = 0.f;
    plProgressMgr::GetInstance()->SetCallbackProc( IProgressMgrCallbackProc );

    // Check the registry, which deletes data files that are either corrupt or
    // have old version numbers.  If the file still exists on the file server
    // then it will be patched on-the-fly as needed (unless you're running with
    // local data of course).
    ((plResManager *)hsgResMgr::ResMgr())->VerifyPages();

    // the dx8 audio system MUST be initialized
    // before the database is loaded
    HWND hWnd = fWindowHndl;
    SetForegroundWindow(fWindowHndl);

    plgAudioSys::Init(hWnd);
    gAudio = plgAudioSys::Sys();

    RegisterAs( kClient_KEY );

    InitDLLs();
    
    plGlobalVisMgr::Init();
    fPageMgr = TRACKED_NEW plPageTreeMgr;

    plVisLOSMgr::Init(fPipeline, fPageMgr);

    // init globals
    plAvatarMgr::GetInstance();
    plRelevanceMgr::Init();

    gDisp = plgDispatch::Dispatch();
    gTimerMgr = plgTimerCallbackMgr::Mgr();

    //
    // initialize input system
    //
    InitInputs();

    /// Init the console object
    /// Note: this can be done last because the console engine was inited first, and
    /// everything in code that works with the console does so through the console engine

    fConsole = TRACKED_NEW pfConsole();
    pfConsole::SetPipeline( fPipeline );
    fConsole->RegisterAs( kConsoleObject_KEY );     // fixedKey from plFixedKey.h
    fConsole->Init( fConsoleEngine );

    /// Init the font cache
    fFontCache = TRACKED_NEW plFontCache();

    /// Init the transition manager
    fTransitionMgr = TRACKED_NEW plTransitionMgr();
    fTransitionMgr->RegisterAs( kTransitionMgr_KEY );       // fixedKey from plFixedKey.h
    fTransitionMgr->Init();

    // Init the Age Linking effects manager
    fLinkEffectsMgr = TRACKED_NEW plLinkEffectsMgr();
    fLinkEffectsMgr->RegisterAs( kLinkEffectsMgr_KEY ); // fixedKey from plFixedKey.h
    fLinkEffectsMgr->Init();
    
    /// Init the in-game GUI manager
    fGameGUIMgr = TRACKED_NEW pfGameGUIMgr();
    fGameGUIMgr->RegisterAs( kGameGUIMgr_KEY );
    fGameGUIMgr->Init();

    plgAudioSys::Activate(true);

    plConst(hsScalar) delay(2.f);
    //commenting out publisher splash for MORE
    //IPlayIntroBink("avi/intro0.bik", delay, 0.f, 0.f, 1.f, 1.f, 0.75);
    //if( GetDone() ) return false;
    IPlayIntroBink("avi/intro1.bik", 0.f, 0.f, 0.f, 1.f, 1.f, 0.75);
    if( GetDone() ) return false;
    plgDispatch::Dispatch()->RegisterForExactType(plMovieMsg::Index(), GetKey());

    //
    // Init Net before loading things
    //
    plgDispatch::Dispatch()->RegisterForExactType(plNetCommAuthMsg::Index(), GetKey());
    plNetClientMgr::GetInstance()->Init();
    plAgeLoader::GetInstance()->Init();
    pfSecurePreloader::GetInstance()->Init();
    
    plCmdIfaceModMsg* pModMsg2 = TRACKED_NEW plCmdIfaceModMsg;
    pModMsg2->SetBCastFlag(plMessage::kBCastByExactType);
    pModMsg2->SetSender(fConsole->GetKey());
    pModMsg2->SetCmd(plCmdIfaceModMsg::kAdd);
    plgDispatch::MsgSend(pModMsg2);

    // create new the virtual camera
    fNewCamera = TRACKED_NEW plVirtualCam1;
    fNewCamera->RegisterAs( kVirtualCamera1_KEY ); 
    fNewCamera->Init();
    fNewCamera->SetPipeline( GetPipeline() );

    float aspectratio = (float)fPipeline->Width() / (float)fPipeline->Height();
    plVirtualCam1::SetAspectRatio(aspectratio);
    plVirtualCam1::SetFOV(plVirtualCam1::GetFOVw(), plVirtualCam1::GetFOVh());

    pfGameGUIMgr::GetInstance()->SetAspectRatio( aspectratio );
    plMouseDevice::Instance()->SetDisplayResolution((float)fPipeline->Width(), (float)fPipeline->Height());

    // create the listener for the audio system:
    plListener* pLMod = TRACKED_NEW plListener;
    pLMod->RegisterAs(kListenerMod_KEY );

    plgDispatch::Dispatch()->RegisterForExactType(plEvalMsg::Index(), pLMod->GetKey());
    plgDispatch::Dispatch()->RegisterForExactType(plAudioSysMsg::Index(), pLMod->GetKey());

    plSynchedObject::PushSynchDisabled(false);      // enable dirty tracking

    if (StrCmp(NetCommGetStartupAge()->ageDatasetName, "StartUp") == 0)
    {
        plNetCommAuthMsg * msg  = NEW(plNetCommAuthMsg);
        msg->result             = kNetSuccess;
        msg->param              = nil;
        msg->Send();
    }

    // 2nd half of plClient initialization occurs after
    // all network events have completed.  Async events:
    //
    // 1) Download secure files
    //
    // Continue plClient init via IOnAsyncInitComplete().

    return true;
}

//============================================================================
void    plClient::IPatchGlobalAgeFiles( void )
{
    const char * ageFiles[] = {
        "GlobalAnimations",
        "GlobalAvatars",
        "GlobalClothing",
        "GlobalMarkers",
        "GUI",
        "CustomAvatars"
    };

    for (unsigned i = 0; i < arrsize(ageFiles); ++i) {
        plResPatcher myPatcher(ageFiles[i], true);

        if (gDataServerLocal)
            break;
            
        if (!myPatcher.Update()) {
            SetDone(true);
            break;
        }
    }
}

void plClient::InitDLLs()
{
    hsStatusMessage("Init dlls client\n");
    char str[255];
    typedef void (*PInitGlobalsFunc) (hsResMgr *, plFactory *, plTimerCallbackManager *, plTimerShare*,
        plNetClientApp*);

    hsFolderIterator modDllFolder("ModDLL\\");
    while (modDllFolder.NextFileSuffix(".dll")) 
    {
        modDllFolder.GetPathAndName(str);
        HMODULE hMod = LoadLibrary(str);
        if (hMod)
        {
            PInitGlobalsFunc initGlobals = (PInitGlobalsFunc)GetProcAddress(hMod, "InitGlobals");
            initGlobals(hsgResMgr::ResMgr(), plFactory::GetTheFactory(), plgTimerCallbackMgr::Mgr(), 
                hsTimer::GetTheTimer(), plNetClientApp::GetInstance());
            fLoadedDLLs.Append(hMod);
        }
    }
}

void plClient::ShutdownDLLs()
{
    int j;
    for( j = 0; j < fLoadedDLLs.GetCount(); j++ )
    {
        BOOL ret = FreeLibrary(fLoadedDLLs[j]);
        if( !ret )
            hsStatusMessage("Failed to free lib\n");
    }
    fLoadedDLLs.Reset();
}

hsBool plClient::MainLoop()
{
#if defined(HAVE_CYPYTHONIDE) && !defined(PLASMA_EXTERNAL_RELEASE)
    if (PythonInterface::UsePythonDebugger())
    {
        PythonInterface::PythonDebugger()->Update();

        if (PythonInterface::PythonDebugger()->IsConnected())
        {
            bPythonDebugConnected = true;
            if (PythonInterface::DebuggerRequestedExit() && PythonInterface::PythonDebugger()->ExitOnStop())
                SetDone(true); // debugger requested that we stop running, so exit nicely
        }
        else
            bPythonDebugConnected = false;
    }
#endif

#ifdef PLASMA_EXTERNAL_RELEASE
    if (DebugIsDebuggerPresent())
    {
        NetCliAuthLogClientDebuggerConnect();
        SetDone(true);
    }
#endif

    if(plClient::fDelayMS)
        Sleep(5);
    
    // Reset our stats
    plProfileManager::Instance().BeginFrame();

    if (IUpdate())
        return true;
        
    if (IDraw())
        return true;
    
    plProfileManagerFull::Instance().EndFrame();
    plProfileManager::Instance().EndFrame();

    // Draw the stats
    plProfileManagerFull::Instance().Update();

    return false;
}

#include "plProfile.h"

plProfile_Extern(DrawTime);
plProfile_Extern(UpdateTime);
plProfile_CreateTimer("ResMgr", "Update", ResMgr);
plProfile_CreateTimer("DispatchQueue", "Update", DispatchQueue);
plProfile_CreateTimer("RenderSetup", "Update", RenderMsg);
plProfile_CreateTimer("Simulation", "Update", Simulation);
plProfile_CreateTimer("NetTime", "Update", UpdateNetTime);
plProfile_Extern(TimeMsg);
plProfile_Extern(EvalMsg);
plProfile_Extern(TransformMsg);
plProfile_Extern(CameraMsg);
plProfile_Extern(AnimatingPhysicals);
plProfile_Extern(StoppedAnimPhysicals);

plProfile_CreateTimer("BeginRender", "Render", BeginRender);
plProfile_CreateTimer("ClearRender", "Render", ClearRender);
plProfile_CreateTimer("PreRender", "Render", PreRender);
plProfile_CreateTimer("MainRender", "Render", MainRender);
plProfile_CreateTimer("PostRender", "Render", PostRender);
plProfile_CreateTimer("Movies", "Render", Movies);
plProfile_CreateTimer("Console", "Render", Console);
plProfile_CreateTimer("StatusLog", "Render", StatusLog);
plProfile_CreateTimer("ProgressMgr", "Render", ProgressMgr);
plProfile_CreateTimer("ScreenElem", "Render", ScreenElem);
plProfile_CreateTimer("EndRender", "Render", EndRender);


hsBool plClient::IUpdate()
{
    plProfile_BeginTiming(UpdateTime);
    
    // reset timer on first frame if realtime and not clamping, to avoid initial large delta
    if (hsTimer::GetSysSeconds()==0 && hsTimer::IsRealTime() && hsTimer::GetTimeClamp()==0)
        hsTimer::SetRealTime(true);

    plProfile_BeginTiming(DispatchQueue);
    plgDispatch::Dispatch()->MsgQueueProcess();
    plProfile_EndTiming(DispatchQueue);
    
    const char *inputUpdate = "Update";
    if (fInputManager) // Is this used anymore? Seems to always be nil.
        fInputManager->Update();

    hsTimer::IncSysSeconds();
    plClientUnifiedTime::SetSysTime();  // keep a unified time, based on sysSeconds
    // Time may have been clamped in IncSysSeconds, depending on hsTimer's current mode.

    double currTime = hsTimer::GetSysSeconds();
    hsScalar delSecs = hsTimer::GetDelSysSeconds();

    // do not change this ordering

    plProfile_BeginTiming(UpdateNetTime);
    plNetClientMgr::GetInstance()->Update(currTime);
    plProfile_EndTiming(UpdateNetTime);

    // update python 
    //plCaptureRender::Update(fPipeline);
    plCaptureRender::Update();
    cyMisc::Update(currTime);

    // This TimeMsg doesn't really do much, except somehow it flushes the dispatch
    // after the NetClientMgr updates, delivering any SelfDestruct messages in the
    // queue. This is important to prevent objects that are about to go away from
    // starting trouble during their update. So to get rid of this message, some
    // other way of flushing the dispatch after NegClientMgr's update is needed. mf 
    plProfile_BeginTiming(TimeMsg);
    plTimeMsg* msg = TRACKED_NEW plTimeMsg(nil, nil, nil, nil);
    plgDispatch::MsgSend(msg);
    plProfile_EndTiming(TimeMsg);

    plProfile_BeginTiming(EvalMsg);
    plEvalMsg* eval = TRACKED_NEW plEvalMsg(nil, nil, nil, nil);
    plgDispatch::MsgSend(eval);
    plProfile_EndTiming(EvalMsg);

    char *xFormLap1 = "Main";
    plProfile_BeginLap(TransformMsg, xFormLap1);
    plTransformMsg* xform = TRACKED_NEW plTransformMsg(nil, nil, nil, nil);
    plgDispatch::MsgSend(xform);
    plProfile_EndLap(TransformMsg, xFormLap1);

    plCoordinateInterface::SetTransformPhase(plCoordinateInterface::kTransformPhaseDelayed);    

    if (fAnimDebugList)
        fAnimDebugList->ShowReport();
    
    plProfile_BeginTiming(Simulation);
    plSimulationMgr::GetInstance()->Advance(delSecs);
    plProfile_EndTiming(Simulation);
            
    // At this point, we just register for a plDelayedTransformMsg when dirtied.
    if (!plCoordinateInterface::GetDelayedTransformsEnabled())
    {
        char *xFormLap2 = "Simulation";
        plProfile_BeginLap(TransformMsg, xFormLap2);
        xform = TRACKED_NEW plTransformMsg(nil, nil, nil, nil);
        plgDispatch::MsgSend(xform);
        plProfile_EndLap(TransformMsg, xFormLap2);
    }
    else
    {
        char *xFormLap3 = "Delayed";
        plProfile_BeginLap(TransformMsg, xFormLap3);
        xform = TRACKED_NEW plDelayedTransformMsg(nil, nil, nil, nil);
        plgDispatch::MsgSend(xform);
        plProfile_EndLap(TransformMsg, xFormLap3);
    }

    plCoordinateInterface::SetTransformPhase(plCoordinateInterface::kTransformPhaseNormal);
    
    plProfile_BeginTiming(CameraMsg);
    plCameraMsg* cameras = TRACKED_NEW plCameraMsg;
    cameras->SetCmd(plCameraMsg::kUpdateCameras);
    cameras->SetBCastFlag(plMessage::kBCastByExactType);
    plgDispatch::MsgSend(cameras);
    plProfile_EndTiming(CameraMsg);

    if (fPatchGlobalAges)
    {
        // Download or patch our global ages, if necessary
        IPatchGlobalAgeFiles();
        IOnAsyncInitComplete();

        fPatchGlobalAges = false;
    }

    return false;
}

hsBool plClient::IDrawProgress() {
    // HACK: Don't draw while we're caching some room loads, otherwise the
    // progress bar will jump around while we're calculating the size
    if (fHoldLoadRequests)
        return false;

    // Reset our stats
    plProfileManager::Instance().BeginFrame();

    plProfile_BeginTiming(DrawTime);
    if( fPipeline->BeginRender() )
    {
        return IFlushRenderRequests();
    }
    // Override the clear color to black.
    fPipeline->ClearRenderTarget(&hsColorRGBA().Set(0.f, 0.f, 0.f, 1.f));

#ifndef PLASMA_EXTERNAL_RELEASE
    fConsole->Draw( fPipeline );
#endif

    plStatusLogMgr::GetInstance().Draw();
    plProgressMgr::GetInstance()->Draw( fPipeline );
    fPipeline->RenderScreenElements();
    fPipeline->EndRender();
    plProfile_EndTiming(DrawTime);

    plProfileManager::Instance().EndFrame();

    return false;
}

hsBool plClient::IDraw()
{
    // Limit framerate
    static float lastDrawTime;
    static const float kMaxFrameRate = 1.f/30.f;
    float currTime = (float) hsTimer::GetSeconds();
    if (!fPipeline->IsDebugFlagSet(plPipeDbg::kFlagNVPerfHUD))
    {
        // If we're using NVPerfHUD to step through draw calls,
        // We're going to have a frame delta of zero. In that
        // case we need to draw no matter what, and we don't
        // care as much about starving other threads because
        // we're presumably just debugging a graphics glitch.
        if ((currTime - lastDrawTime) < kMaxFrameRate)
            return true;
    }
    lastDrawTime = currTime;

    // If we're shutting down, don't attempt to draw.  Doing so
    // tends to cause a device reload each frame.   
    if (fDone)
        return true;
    
    if (plProgressMgr::GetInstance()->IsActive())
        return IDrawProgress();
        
    plProfile_Extern(VisEval);
    plProfile_BeginTiming(VisEval);
    plGlobalVisMgr::Instance()->Eval(fPipeline->GetViewPositionWorld());
    plProfile_EndTiming(VisEval);

    plProfile_BeginTiming(RenderMsg);
    plRenderMsg* rendMsg = TRACKED_NEW plRenderMsg(fPipeline);
    plgDispatch::MsgSend(rendMsg);
    plProfile_EndTiming(RenderMsg);

    plPreResourceMsg* preMsg = TRACKED_NEW plPreResourceMsg(fPipeline);
    plgDispatch::MsgSend(preMsg);

    // This might not be the ideal place for this, but it 
    // needs to be AFTER the plRenderMsg is sent, and
    // BEFORE BeginRender. (plRenderMsg causes construction of
    // Dynamic objects (e.g. RT's), BeginRender uses them (e.g. shadows).
    if( plPipeResReq::Check() || fPipeline->CheckResources() )
    {
        fPipeline->LoadResources();
    }

    plProfile_EndTiming(UpdateTime);

    plProfile_BeginTiming(DrawTime);

    plProfile_BeginTiming(BeginRender);
    if( fPipeline->BeginRender() )
    {
        plProfile_EndTiming(BeginRender);
        return IFlushRenderRequests();
    }
    plProfile_EndTiming(BeginRender);

    plProfile_BeginTiming(ClearRender);
    fPipeline->ClearRenderTarget();
    plProfile_EndTiming(ClearRender);

    plProfile_BeginTiming(PreRender);
    if( !fFlags.IsBitSet( kFlagDBGDisableRRequests ) )
        IProcessPreRenderRequests();
    plProfile_EndTiming(PreRender);

    plProfile_BeginTiming(MainRender);
    if( !fFlags.IsBitSet( kFlagDBGDisableRender ) )
        fPageMgr->Render(fPipeline);
    plProfile_EndTiming(MainRender);

    plProfile_BeginTiming(PostRender);
    if( !fFlags.IsBitSet( kFlagDBGDisableRRequests ) )
        IProcessPostRenderRequests();
    plProfile_EndTiming(PostRender);

    plProfile_BeginTiming(Movies);
    IServiceMovies();
    plProfile_EndTiming(Movies);

#ifndef PLASMA_EXTERNAL_RELEASE
    plProfile_BeginTiming(Console);
    fConsole->Draw( fPipeline );
    plProfile_EndTiming(Console);
#endif

    plProfile_BeginTiming(StatusLog);
    plStatusLogMgr::GetInstance().Draw();
    plProfile_EndTiming(StatusLog);

    plProfile_BeginTiming(ProgressMgr);
    plProgressMgr::GetInstance()->Draw( fPipeline );
    plProfile_EndTiming(ProgressMgr);

    fLastProgressUpdate = hsTimer::GetSeconds();

    plProfile_BeginTiming(ScreenElem);
    fPipeline->RenderScreenElements();
    plProfile_EndTiming(ScreenElem);

    plProfile_BeginTiming(EndRender);
    fPipeline->EndRender();
    plProfile_EndTiming(EndRender);

    plProfile_EndTiming(DrawTime); 

    return false;
}

void plClient::IServiceMovies()
{
    int i;
    for( i = 0; i < fMovies.GetCount(); i++ )
    {
        hsAssert(fMovies[i]->GetFileName() && *fMovies[i]->GetFileName(), "Lost our movie");
        if( !fMovies[i]->NextFrame() )
        {
            delete fMovies[i];
            fMovies.Remove(i);
            i--;
        }
    }
}

void plClient::IKillMovies()
{
    int i;
    for( i = 0; i < fMovies.GetCount(); i++ )
        delete fMovies[i];
    fMovies.Reset();
}

hsBool plClient::IPlayIntroBink(const char* movieName, hsScalar endDelay, hsScalar posX, hsScalar posY, hsScalar scaleX, hsScalar scaleY, hsScalar volume /* = 1.0 */)
{
    SetQuitIntro(false);
    plBinkPlayer player;
    player.SetPosition(posX, posY);
    player.SetScale(scaleX, scaleY);
    player.SetFileName(movieName);
    player.SetFadeToTime(endDelay);
    player.SetFadeToColor(hsColorRGBA().Set(0, 0, 0, 1.f));
    player.SetVolume(volume);
    bool firstTry = true;  // flag to make sure that we don't quit before we even start

    if( player.Start(fPipeline, fWindowHndl) )
    {
        while( true )
        {
            if( fInstance )
                fInstance->fMessagePumpProc();

            if( GetDone() )
                return true;
            if (firstTry)
            {
                firstTry = false;
                SetQuitIntro(false);
            }
            else
            {
                if( GetQuitIntro() )
                    return true;
            }

            hsBool done = false;
            if( !fPipeline->BeginRender() )
            {
                fPipeline->ClearRenderTarget();
                done = !player.NextFrame();

                fPipeline->EndRender();
            }

            if( done )
                return true;
        }
        return true;
    }
    return false;
}

hsBool plClient::IFlushRenderRequests()
{
    // For those requesting ack's, we could go through and send them
    // mail telling them their request was ill-timed. But hopefully,
    // the lack of an acknowledgement will serve as notice.
    int i;
    for( i = 0; i < fPreRenderRequests.GetCount(); i++ )
        hsRefCnt_SafeUnRef(fPreRenderRequests[i]);
    fPreRenderRequests.Reset();

    for( i = 0; i < fPostRenderRequests.GetCount(); i++ )
        hsRefCnt_SafeUnRef(fPostRenderRequests[i]);
    fPostRenderRequests.Reset();

    return false;
}

void plClient::IProcessRenderRequests(hsTArray<plRenderRequest*>& reqs)
{
    int i;
    for( i = 0; i < reqs.GetCount(); i++ )
    {
        reqs[i]->Render(fPipeline, fPageMgr);
        hsRefCnt_SafeUnRef(reqs[i]);
    }
    reqs.SetCount(0);
}

void plClient::IProcessPreRenderRequests()
{
    IProcessRenderRequests(fPreRenderRequests);
}

void plClient::IProcessPostRenderRequests()
{
    IProcessRenderRequests(fPostRenderRequests);
}

void plClient::IAddRenderRequest(plRenderRequest* req)
{
    if( req->GetPriority() < 0 )
    {
        int i;
        for( i = 0; i < fPreRenderRequests.GetCount(); i++ )
        {
            if( req->GetPriority() < fPreRenderRequests[i]->GetPriority() )
                break;
        }
        fPreRenderRequests.Insert(i, req);
        hsRefCnt_SafeRef(req);
    }
    else
    {
        int i;
        for( i = 0; i < fPostRenderRequests.GetCount(); i++ )
        {
            if( req->GetPriority() < fPostRenderRequests[i]->GetPriority() )
                break;
        }
        fPostRenderRequests.Insert(i, req);
        hsRefCnt_SafeRef(req);
    }
}

hsG3DDeviceModeRecord plClient::ILoadDevMode(const char* devModeFile)
{
    hsStatusMessage("Load DevMode client\n");
    HWND hWnd = fWindowHndl;

    hsUNIXStream    stream;
    hsBool          gottaCreate = false;

    // If DevModeFind is specified, use the old method
//  if ((GetGameFlags() & kDevModeFind))
//      FindAndSaveDevMode(hWnd, devModeFile);
    // Otherwise, use the new method
    hsG3DDeviceModeRecord dmr;
    if (stream.Open(devModeFile, "rb"))
    {
        /// It's there, but is the device record valid?
        hsG3DDeviceRecord selRec;
        hsG3DDeviceMode selMode;

        selRec.Read(&stream);
        if( selRec.IsInvalid() )
        {
            hsStatusMessage( "WARNING: Old DeviceRecord found on file. Setting defaults..." );
            gottaCreate = true;
        }
        else
        {
            /// Read the rest in
            selMode.Read(&stream);

            UInt16 performance = stream.ReadSwap16();

            if( performance < 25 )
                plBitmap::SetGlobalLevelChopCount( 2 );
            else if( performance < 75 )
                plBitmap::SetGlobalLevelChopCount( 1 );
            else
                plBitmap::SetGlobalLevelChopCount( 0 );
        }
        stream.Close();

        dmr = hsG3DDeviceModeRecord(selRec, selMode);
    }
    else
        gottaCreate = true;

    if( gottaCreate )
    {

        hsG3DDeviceSelector devSel;
        devSel.Enumerate(hWnd);
        devSel.RemoveUnusableDevModes(true);

        if (!devSel.GetDefault(&dmr))
        {
            //hsAssert(0, "plGame::LoadDevMode - No acceptable hardware found");
            hsMessageBox("No suitable rendering devices found.","realMYST",hsMessageBoxNormal);
            return dmr;
        }

        if (stream.Open(devModeFile, "wb"))
        {
            dmr.GetDevice()->Write(&stream);
            dmr.GetMode()->Write(&stream);
            stream.WriteSwap16((UInt16)(0*100));
            stream.Close();
        }

    }
    return dmr;
}

void plClient::ResetDisplayDevice(int Width, int Height, int ColorDepth, hsBool Windowed, int NumAASamples, int MaxAnisotropicSamples, hsBool VSync, hsBool windowOnly)
{
    if(!fPipeline) return;
    int BorderWidth = 0, BorderHeight = 0, CaptionHeight = 0;

    WindowActivate(false);
    int ActualWidth;
    int ActualHeight;

    if( Windowed )
    {
        BorderWidth = GetSystemMetrics( SM_CXSIZEFRAME );
        BorderHeight = GetSystemMetrics( SM_CYSIZEFRAME );
        CaptionHeight = GetSystemMetrics( SM_CYCAPTION );
        ActualWidth = Width + BorderWidth * 2;
        ActualHeight = Height + BorderHeight * 2 + CaptionHeight;
        SetWindowLong( fWindowHndl, GWL_STYLE,
            WS_POPUP | WS_OVERLAPPEDWINDOW | WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_VISIBLE);
        SetWindowLong(fWindowHndl, GWL_EXSTYLE, 0);
    }
    else
    {
        SetWindowLong(fWindowHndl, GWL_STYLE,
            WS_POPUP | WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_MAXIMIZE | WS_VISIBLE);

        SetWindowLong(fWindowHndl, GWL_EXSTYLE, WS_EX_APPWINDOW);
    }

    if(!windowOnly)
        fPipeline->ResetDisplayDevice(Width, Height, ColorDepth, Windowed, NumAASamples, MaxAnisotropicSamples, VSync);

    float aspectratio = (float)Width / (float)Height;
    plMouseDevice::Instance()->SetDisplayResolution((float)Width, (float)Height);
    pfGameGUIMgr::GetInstance()->SetAspectRatio( aspectratio );

    UINT flags = SWP_NOCOPYBITS | SWP_NOMOVE | SWP_SHOWWINDOW;
    if(Windowed)
    {
        SetWindowPos( fWindowHndl, HWND_NOTOPMOST, 0, 0, ActualWidth, ActualHeight, flags );
    }
    else
    {
        SetWindowPos( fWindowHndl, HWND_TOP, 0, 0, Width, Height, flags );
        ::ClipCursor(nil);
    }

    WindowActivate(true);
}


void WriteBool(hsStream *stream, char *name, hsBool on )
{
    char command[256];
    sprintf(command, "%s %s\r\n", name, on ? "true" : "false");
    stream->WriteString(command);
}

void WriteInt(hsStream *stream, char *name, int val )
{
    char command[256];
    sprintf(command, "%s %d\r\n", name, val);
    stream->WriteString(command);
}

void WriteString(hsStream *stream, const char *name, const char *val)
{
    char command[256];
    sprintf(command, "%s %s\r\n", name, val);
    stream->WriteString(command);
}

// Detect audio/video settings and save them to their respective ini file, if ini files don't exist
void plClient::IDetectAudioVideoSettings()
{
    // Setup default pipeline settings
    hsBool devmode = true;
    hsG3DDeviceModeRecord dmr;
    hsG3DDeviceSelector devSel;
    devSel.Enumerate(fWindowHndl);
    devSel.RemoveUnusableDevModes(true);

    if (!devSel.GetDefault(&dmr))
        devmode = false;
    hsG3DDeviceRecord *rec = (hsG3DDeviceRecord *)dmr.GetDevice();
    const hsG3DDeviceMode *mode = dmr.GetMode();

    hsBool pixelshaders = rec->GetCap(hsG3DDeviceSelector::kCapsPixelShader);
    int psMajor = 0, psMinor = 0;
    rec->GetPixelShaderVersion(psMajor, psMinor);
    hsBool refDevice = false;
    if(rec->GetG3DHALorHEL() == hsG3DDeviceSelector::kHHD3DRefDev)
        refDevice = true;

    plPipeline::fDefaultPipeParams.Width = hsG3DDeviceSelector::kDefaultWidth;
    plPipeline::fDefaultPipeParams.Height = hsG3DDeviceSelector::kDefaultHeight;
    plPipeline::fDefaultPipeParams.ColorDepth = hsG3DDeviceSelector::kDefaultDepth;
#if defined(HS_DEBUGGING) || defined(DEBUG)
    plPipeline::fDefaultPipeParams.Windowed = true;
#else
    plPipeline::fDefaultPipeParams.Windowed = false;
#endif

    plPipeline::fDefaultPipeParams.Shadows = 0;
    // enable shadows if TnL is available, meaning not an intel extreme.
    if(rec->GetG3DHALorHEL() == hsG3DDeviceSelector::kHHD3DTnLHalDev)
        plPipeline::fDefaultPipeParams.Shadows = 1;

    // enable planar reflections if pixelshaders are available
    if(pixelshaders && !refDevice)
    {
    plPipeline::fDefaultPipeParams.PlanarReflections = 1;
    }
    else
    {
    plPipeline::fDefaultPipeParams.PlanarReflections = 0;
    }

    // enable 2x antialiasing and anisotropic to 2 samples if pixelshader version is greater that 2.0
    if(psMajor >= 2 && !refDevice)
    {
        plPipeline::fDefaultPipeParams.AntiAliasingAmount = rec->GetMaxAnisotropicSamples() ? 2 : 0;
        plPipeline::fDefaultPipeParams.AnisotropicLevel = mode->GetNumFSAATypes() ? 2 : 0;
    }
    else
    {
        plPipeline::fDefaultPipeParams.AntiAliasingAmount = 0;
        plPipeline::fDefaultPipeParams.AnisotropicLevel = 0;
    }

    if(refDevice)
    {
        plPipeline::fDefaultPipeParams.TextureQuality = 0;
        plPipeline::fDefaultPipeParams.VideoQuality = 0;

    }
    else
    {
        plPipeline::fDefaultPipeParams.TextureQuality = psMajor >= 2 ? 2 : 1;
        plPipeline::fDefaultPipeParams.VideoQuality = pixelshaders ? 2 : 1;
    }
    plPipeline::fDefaultPipeParams.VSync = false;

    // card specific overrides
    if(strstr(rec->GetDriverDesc(), "FX 5200"))
    {
        plPipeline::fDefaultPipeParams.AntiAliasingAmount = 0;
    }


    int val = 0;
    hsStream *stream = nil;
    hsUNIXStream s;
    wchar audioIniFile[MAX_PATH], graphicsIniFile[MAX_PATH];
    PathGetInitDirectory(audioIniFile, arrsize(audioIniFile));
    StrCopy(graphicsIniFile, audioIniFile, arrsize(audioIniFile));
    PathAddFilename(audioIniFile, audioIniFile, L"audio.ini", arrsize(audioIniFile));
    PathAddFilename(graphicsIniFile, graphicsIniFile, L"graphics.ini", arrsize(graphicsIniFile));

#ifndef PLASMA_EXTERNAL_RELEASE
    // internal builds can use the local dir
    if (PathDoesFileExist(L"init//audio.ini"))
        StrCopy(audioIniFile, L"init//audio.ini", arrsize(audioIniFile));
    if (PathDoesFileExist(L"init//graphics.ini"))
        StrCopy(graphicsIniFile, L"init//graphics.ini", arrsize(audioIniFile));
#endif

    //check to see if audio.ini exists
    if (s.Open(audioIniFile))
    {
        s.Close();
    }
    else
    {
        stream = plEncryptedStream::OpenEncryptedFileWrite(audioIniFile);

        plAudioCaps caps = plAudioCapsDetector::Detect(false, true);
        val = 6;
        if( (hsPhysicalMemory() < 256) || plProfileManager::Instance().GetProcessorSpeed() < 1350000000)
        {
            val = 3;
        }

        char deviceName[256];
        sprintf(deviceName, "\"%s\"", DEFAULT_AUDIO_DEVICE_NAME);

        WriteBool(stream, "Audio.Initialize",  caps.IsAvailable());
        WriteBool(stream, "Audio.UseEAX", false);
        WriteInt(stream, "Audio.SetPriorityCutoff", val);
        WriteInt(stream, "Audio.MuteAll", false);
        WriteInt(stream, "Audio.SetChannelVolume SoundFX", 1);
        WriteInt(stream, "Audio.SetChannelVolume BgndMusic", 1);
        WriteInt(stream, "Audio.SetChannelVolume Ambience", 1);
        WriteInt(stream, "Audio.SetChannelVolume NPCVoice", 1);
        WriteInt(stream, "Audio.EnableVoiceRecording", 1);
        WriteString(stream, "Audio.SetDeviceName", deviceName );
        stream->Close();
        delete stream;
        stream = nil;
    }
    
    // check to see if graphics.ini exists
    if (s.Open(graphicsIniFile))
    {
        s.Close();
    }
    else
    {
        IWriteDefaultGraphicsSettings(graphicsIniFile);
    }
}

void plClient::IWriteDefaultGraphicsSettings(const wchar* destFile)
{
    hsStream *stream = plEncryptedStream::OpenEncryptedFileWrite(destFile);

    WriteInt(stream, "Graphics.Width", plPipeline::fDefaultPipeParams.Width);
    WriteInt(stream, "Graphics.Height", plPipeline::fDefaultPipeParams.Height);
    WriteInt(stream, "Graphics.ColorDepth", plPipeline::fDefaultPipeParams.ColorDepth);
    WriteBool(stream, "Graphics.Windowed", plPipeline::fDefaultPipeParams.Windowed);
    WriteInt(stream, "Graphics.AntiAliasAmount", plPipeline::fDefaultPipeParams.AntiAliasingAmount);
    WriteInt(stream, "Graphics.AnisotropicLevel", plPipeline::fDefaultPipeParams.AnisotropicLevel );
    WriteInt(stream, "Graphics.TextureQuality",plPipeline::fDefaultPipeParams.TextureQuality);
    WriteInt(stream, "Quality.Level", plPipeline::fDefaultPipeParams.VideoQuality);
    WriteInt(stream, "Graphics.Shadow.Enable", plPipeline::fDefaultPipeParams.Shadows);
    WriteInt(stream, "Graphics.EnablePlanarReflections", plPipeline::fDefaultPipeParams.PlanarReflections);
    WriteBool(stream, "Graphics.EnableVSync", plPipeline::fDefaultPipeParams.VSync);
    stream->Close();
    delete stream;
    stream = nil;
}


void plClient::WindowActivate(bool active)
{
    if (GetDone())
        return;
        
    if( !fWindowActive != !active )
    {
        if( fInputManager != nil )
            fInputManager->Activate( active );

        plArmatureMod::WindowActivate( active );
    }
    fWindowActive = active;
}


//============================================================================
void plClient::IOnAsyncInitComplete () {
    // Init State Desc Language (files should now be downloaded and in place)
    plSDLMgr::GetInstance()->SetNetApp(plNetClientMgr::GetInstance());
    plSDLMgr::GetInstance()->Init( plSDL::kDisallowTimeStamping );

    PythonInterface::initPython();
    // set the pipeline for the python cyMisc module so that it can do a screen capture
    cyMisc::SetPipeline( fPipeline );

    // Load our custom fonts from our current dat directory
    fFontCache->LoadCustomFonts("dat");
    plWinFontCache::GetInstance().LoadCustomFonts("dat");

    // We'd like to do a SetHoldLoadRequests here, but the GUI stuff doesn't draw right
    // if you try to delay the loading for it.  To work around that, we allocate a
    // global loading bar in advance and set it to a big enough range that when the GUI's
    // are done loading about the right amount of it is filled.
    fNumLoadingRooms++;
    IStartProgress("Loading Global...", 0);

    /// Init the KI
    pfGameGUIMgr    *mgr = pfGameGUIMgr::GetInstance();
    mgr->LoadDialog( "KIBlackBar" );    // load the blackbar which will bootstrap in the rest of the KI dialogs

    // Init the journal book API
    pfJournalBook::SingletonInit();

    SetHoldLoadRequests(true);
    fProgressBar->SetLength(fProgressBar->GetProgress());

    plClothingMgr::Init();
    // Load in any clothing data
    ((plResManager*)hsgResMgr::ResMgr())->PageInAge("GlobalClothing");

    pfMarkerMgr::Instance();

    fAnimDebugList = TRACKED_NEW plAnimDebugList();

    /// Now parse final init files (*.fni). These are files just like ini files, only to be run
    /// after all hell has broken loose in the client.
    wchar initFolder[MAX_PATH];
    PathGetInitDirectory(initFolder, arrsize(initFolder));
    pfConsoleDirSrc     dirSrc( fConsoleEngine, initFolder, L"net*.fni" );  // connect to net first
#ifndef PLASMA_EXTERNAL_RELEASE
    // internal builds also parse the local init folder
    dirSrc.ParseDirectory( L"init", L"net*.fni" );
#endif

    dirSrc.ParseDirectory( initFolder, L"*.fni" );
#ifndef PLASMA_EXTERNAL_RELEASE
    // internal builds also parse the local init folder
    dirSrc.ParseDirectory( L"init", L"*.fni" );
#endif

    // run fni in the Aux Init dir
    if (fpAuxInitDir)
    {   
        dirSrc.ParseDirectory(fpAuxInitDir, "net*.fni" );   // connect to net first
        dirSrc.ParseDirectory(fpAuxInitDir, "*.fni" );
    }

    fNumLoadingRooms--;

    ((plResManager*)hsgResMgr::ResMgr())->PageInAge("GlobalAnimations");
    SetHoldLoadRequests(false);

    // Tell the transition manager to start faded out. This is so we don't
    // get a frame or two of non-faded drawing before we do our initial fade in
    (void)(TRACKED_NEW plTransitionMsg( plTransitionMsg::kFadeOut, 0.0f, true ))->Send();

    fFlags.SetBit(kFlagAsyncInitComplete);
    if (fFlags.IsBitSet(kFlagGlobalDataLoaded))
        ICompleteInit();
}

//============================================================================
void plClient::ICompleteInit () {
    // Reset clear color on the pipeline
//  fPipeline->ClearRenderTarget( &fClearColor, &depth );

    plSimulationMgr::GetInstance()->Resume();               // start the sim at the last possible minute

    fFlags.SetBit( kFlagIniting, false );
    hsStatusMessage("Client init complete.");

    // Tell everyone we're ready to rock.
    plClientMsg* clientMsg = TRACKED_NEW plClientMsg(plClientMsg::kInitComplete);
    clientMsg->SetBCastFlag(plMessage::kBCastByType);
    clientMsg->Send();

    CsrSrvInitialize();
}

//============================================================================
void plClient::IHandlePreloaderMsg (plPreloaderMsg * msg) {

    plgDispatch::Dispatch()->UnRegisterForExactType(plPreloaderMsg::Index(), GetKey());
    
    if (!msg->fSuccess) {
        char str[1024];
        StrPrintf(
            str,
            arrsize(str),
            "Secure file preloader failed"
        );
        plNetClientApp::GetInstance()->QueueDisableNet(true, str);
        return;
    }
    
    fPatchGlobalAges = true;
}

//============================================================================
void plClient::IHandleNetCommAuthMsg (plNetCommAuthMsg * msg) {

    plgDispatch::Dispatch()->UnRegisterForExactType(plNetCommAuthMsg::Index(), GetKey());

    if (IS_NET_ERROR(msg->result)) {
        char str[1024];
        StrPrintf(
            str,
            arrsize(str),
            // fmt
            "Authentication failed: NetError %u, %S.\n"
            ,// values
            msg->result,
            NetErrorToString(msg->result)
        );
        plNetClientApp::GetInstance()->QueueDisableNet(true, str);
        return;
    }

    plgDispatch::Dispatch()->RegisterForExactType(plPreloaderMsg::Index(), GetKey());

    // Precache our secure files
    pfSecurePreloader::GetInstance()->RequestFileGroup(L"Python", L"pak");
    pfSecurePreloader::GetInstance()->RequestFileGroup(L"SDL", L"sdl");
    pfSecurePreloader::GetInstance()->Start();
}