Beispiel #1
0
bool plNetObjectDebugger::AddDebugObject(const char* objName, const char* pageName)
{
    if (!objName)
        return false;

    int size=strlen(objName)+1;
    hsTempArray<char> tmpObjName(size);
    memset(tmpObjName, 0, size);

    //
    // set string matching flags
    //
    int len = strlen(objName);
    uint32_t flags=0;
    if (objName[0]=='*')
    {
        if (objName[len-1]=='*')
        {
            flags = kSubStringMatch;    // *foo*
            strncpy(tmpObjName, objName+1, strlen(objName)-2);
        }
        else
        {
            flags = kEndStringMatch;    // *foo
            strncpy(tmpObjName, objName+1, strlen(objName)-1);
        }
    }

    if (!flags && objName[len-1]=='*')
    {
        flags = kStartStringMatch;      // foo*
        strncpy(tmpObjName, objName, strlen(objName)-1);
    }

    if (!flags)
    {
        flags = kExactStringMatch;
        strcpy(tmpObjName, objName);
    }

    //
    // set plLocation
    //
    plLocation loc;
    if (pageName)
    {
        loc = plKeyFinder::Instance().FindLocation(NetCommGetAge()->ageDatasetName, pageName);
        flags |= kPageMatch;
    }

    fDebugObjects.push_back(new DebugObject(tmpObjName, loc, flags));

    ICreateStatusLog();

    return true;
}
Beispiel #2
0
//
// if both objName and pageName are provided, and this object has page info,
//      return true if object matches both string and location.
// else just return true if object matches string
// 
bool plNetObjectDebugger::DebugObject::ObjectMatches(const char* objName, const char* pageName)
{
    if (!objName)
        return false;

    if (!pageName || (fFlags & kPageMatch)==0)
    {
        // only have enough info to match by objName
        return StringMatches(objName);
    }

    plLocation loc;
    loc = plKeyFinder::Instance().FindLocation(NetCommGetAge()->ageDatasetName, pageName);
    return (StringMatches(objName) && loc==fLoc);
}
Beispiel #3
0
void plAutoProfileImp::INextProfile()
{
    // Haven't linked to our first age yet, do that before we start profiling
    if (fNextAge == 0)
    {
        if (!INextAge())
            IShutdown();
    }
    else
    {
        // Log the stats for this spawn point
        if (!fLastSpawnPointName.IsNull())
        {
            plString ageName = NetCommGetAge()->ageDatasetName;
            plProfileManagerFull::Instance().LogStats(ageName, fLastSpawnPointName);

            plMipmap mipmap;
            if (plClient::GetInstance()->GetPipeline()->CaptureScreen(&mipmap))
            {
                plString fileName = plFormat("{}\\{}_{}.jpg",
                    plProfileManagerFull::Instance().GetProfilePath(),
                    ageName, fLastSpawnPointName);

                plJPEG::Instance().SetWriteQuality(100);
                plJPEG::Instance().WriteToFile(fileName.c_str(), &mipmap);
            }

            fLastSpawnPointName = plString::Null;
        }

        // Try to go to the next spawn point
        if (!INextSpawnPoint())
        {
            // Link to the next age
            if (!INextAge())
            {
                // We've done all the ages, shut down
                IShutdown();
            }
        }
    }
}
Beispiel #4
0
void plLinkEffectsMgr::ISendAllReadyCallbacks()
{
    int i;
    for (i = fLinks.GetCount() - 1; i >= 0; i--)
    {
        if (fLinks[i]->fEffects <= 0)
        {
            if (fLinks[i]->IsLeavingAge())
            {
                if (fLinks[i]->GetLinkKey() == plNetClientApp::GetInstance()->GetLocalPlayerKey())
                {
                    plLinkOutUnloadMsg* lam = new plLinkOutUnloadMsg;   // derived from LoadAgeMsg
                    lam->SetAgeFilename( NetCommGetAge()->ageDatasetName );
                    lam->AddReceiver(plNetClientMgr::GetInstance()->GetKey());
                    lam->SetPlayerID(plNetClientMgr::GetInstance()->GetPlayerID());
                    lam->Send();
                }
            }
            else
            {
                plLinkInDoneMsg* lid = new plLinkInDoneMsg;
                lid->AddReceiver(fLinks[i]->GetLinkKey());
                lid->SetBCastFlag(plMessage::kPropagateToModifiers);
                lid->Send();                    

                if (fLinks[i]->GetLinkKey() == plNetClientApp::GetInstance()->GetLocalPlayerKey())
                {
                    plLinkInDoneMsg* lid = new plLinkInDoneMsg;
                    lid->AddReceiver(plNetClientMgr::GetInstance()->GetKey());
                    lid->Send();
                }
            }

            hsRefCnt_SafeUnRef(fLinks[i]);
            fLinks.Remove(i);

            hsStatusMessage("Done - removing link FX msg\n");
        }
    }
}
Beispiel #5
0
uint8_t plNetLinkingMgr::IPreProcessLink(void)
{
    // Grab some stuff we're gonna use extensively
    plNetClientMgr* nc = plNetClientMgr::GetInstance();
    plAgeLinkStruct* link = GetAgeLink();
    plAgeInfoStruct* info = link->GetAgeInfo();

    PreProcessResult success = kLinkImmediately;

    if ( nc->GetFlagsBit( plNetClientMgr::kNullSend ) )
    {
        hsLogEntry( nc->DebugMsg( "NetClientMgr nullsend. Not linking." ) );
        return kLinkFailed;
    }

#if 0
    // Appear offline until we're done linking
    if (RelVaultNode * rvnInfo = VaultGetPlayerInfoNodeIncRef()) {
        VaultPlayerInfoNode accInfo(rvnInfo);
        accInfo.SetAgeInstName(nil);
        accInfo.SetAgeInstUuid(kNilUuid);
        accInfo.SetOnline(false);
        rvnInfo->DecRef();
    }
#else
    // Update our online status 
    if (RelVaultNode * rvnInfo = VaultGetPlayerInfoNodeIncRef()) {
        VaultPlayerInfoNode accInfo(rvnInfo);
        wchar_t ageInstName[MAX_PATH];
        plUUID ageInstGuid = *GetAgeLink()->GetAgeInfo()->GetAgeInstanceGuid();
        StrToUnicode(ageInstName, info->GetAgeInstanceName(), arrsize(ageInstName));
        accInfo.SetAgeInstName(ageInstName);
        accInfo.SetAgeInstUuid(ageInstGuid);
        accInfo.SetOnline(true);
        rvnInfo->DecRef();
    }
#endif

    //------------------------------------------------------------------------
    // Fixup empty fields
    if (info->HasAgeFilename())
    {
        info->SetAgeFilename(plNetLinkingMgr::GetProperAgeName(info->GetAgeFilename()).c_str());

        if (!info->HasAgeInstanceName())
        {
            info->SetAgeInstanceName(info->GetAgeFilename());
        }
    }

    hsLogEntry(nc->DebugMsg( "plNetLinkingMgr: Pre-Process: Linking with %s rules...",
        plNetCommon::LinkingRules::LinkingRuleStr(link->GetLinkingRules())));

    //------------------------------------------------------------------------
    // SPECIAL CASE: StartUp: force basic link
    if (info->GetAgeFilename().CompareI(kStartUpAgeFilename) == 0)
        link->SetLinkingRules( plNetCommon::LinkingRules::kBasicLink );

    //------------------------------------------------------------------------
    // SPECIAL CASE: Nexus: force original link
    if (info->GetAgeFilename().CompareI(kNexusAgeFilename) == 0)
        link->SetLinkingRules(plNetCommon::LinkingRules::kOriginalBook);

    //------------------------------------------------------------------------
    // SPECIAL CASE: ACA: force original link
    if (info->GetAgeFilename().CompareI(kAvCustomizationFilename) == 0)
        link->SetLinkingRules(plNetCommon::LinkingRules::kOriginalBook);

    hsLogEntry(nc->DebugMsg("plNetLinkingMgr: Process: Linking with %s rules...",
        plNetCommon::LinkingRules::LinkingRuleStr(link->GetLinkingRules())));

    switch (link->GetLinkingRules())
    {
        //--------------------------------------------------------------------
        // BASIC LINK. Link to a unique instance of the age, if no instance specified.
        case plNetCommon::LinkingRules::kBasicLink:
            if (!info->HasAgeInstanceGuid()) {
                plUUID newuuid = plUUID::Generate();
                info->SetAgeInstanceGuid(&newuuid);
            }
        break;

        //--------------------------------------------------------------------
        // ORIGINAL BOOK.  Become an owner of the age, if not already one.
        case plNetCommon::LinkingRules::kOriginalBook:
            {
                // create a new ageinfo struct with the filename of the age because
                // we just want to find out if we own *any* link to the age, not the specific
                // link that we're linking through
                plAgeInfoStruct ageInfo;
                ageInfo.SetAgeFilename(info->GetAgeFilename());

                plAgeLinkStruct ownedLink;
                if (!VaultGetOwnedAgeLink(&ageInfo, &ownedLink)) {
                    // Fill in fields for new age create.
                    if (!info->HasAgeUserDefinedName())
                    {
                        // set user-defined name
                        plString title;
                        unsigned nameLen = nc->GetPlayerName().GetSize();
                        if (nc->GetPlayerName().ToLower().CharAt(nameLen - 1) == 's')
                            title = plString::Format("%s'", nc->GetPlayerName().c_str());
                        else
                            title = plString::Format("%s's", nc->GetPlayerName().c_str());
                        info->SetAgeUserDefinedName(title.c_str());
                    }
                    if (!info->HasAgeDescription())
                    {
                        // set description
                        plString desc;
                        unsigned nameLen = nc->GetPlayerName().GetSize();
                        if (nc->GetPlayerName().ToLower().CharAt(nameLen - 1) == 's')
                            desc = plString::Format("%s' %s", nc->GetPlayerName().c_str(), info->GetAgeInstanceName().c_str());
                        else
                            desc = plString::Format("%s's %s", nc->GetPlayerName().c_str(), info->GetAgeInstanceName().c_str());
                        info->SetAgeDescription(desc.c_str());
                    }
                    if (!info->HasAgeInstanceGuid()) {
                        plUUID newuuid = plUUID::Generate();
                        info->SetAgeInstanceGuid(&newuuid);
                    }
                    
                    // register this as an owned age now before we link to it.
                    // Note: We MUST break or the OwnedBook code will fail!
                    VaultRegisterOwnedAge(link);
                    success = kLinkDeferred;
                    break;
                }
                else if (RelVaultNode* linkNode = VaultGetOwnedAgeLinkIncRef(&ageInfo)) {
                    // We have the age in our AgesIOwnFolder. If its volatile, dump it for the new one.
                    VaultAgeLinkNode linkAcc(linkNode);
                    if (linkAcc.GetVolatile()) {
                        if (VaultUnregisterOwnedAgeAndWait(&ageInfo)) {
                            // Fill in fields for new age create.
                            if (!info->HasAgeUserDefinedName())
                            {
                                // set user-defined name
                                plString title;
                                unsigned nameLen = nc->GetPlayerName().GetSize();
                                if (nc->GetPlayerName().ToLower().CharAt(nameLen - 1) == 's')
                                    title = plString::Format("%s'", nc->GetPlayerName().c_str());
                                else
                                    title = plString::Format("%s's", nc->GetPlayerName().c_str());
                                info->SetAgeUserDefinedName(title.c_str());
                            }

                            if (!info->HasAgeDescription())
                            {
                                // set description
                                plString desc;
                                unsigned nameLen = nc->GetPlayerName().GetSize();
                                if (nc->GetPlayerName().ToLower().CharAt(nameLen - 1) == 's')
                                    desc = plString::Format("%s' %s", nc->GetPlayerName().c_str(), info->GetAgeInstanceName().c_str());
                                else
                                    desc = plString::Format("%s's %s", nc->GetPlayerName().c_str(), info->GetAgeInstanceName().c_str());
                                info->SetAgeDescription( desc.c_str() );
                            }

                            if (!info->HasAgeInstanceGuid()) {
                                plUUID newuuid = plUUID::Generate();
                                info->SetAgeInstanceGuid(&newuuid);
                            }

                            VaultRegisterOwnedAge(link);

                            // Note: We MUST break or the OwnedBook code will fail!
                            success = kLinkDeferred;
                            break;
                        }
                    }
                    else {
                        if (info->GetAgeFilename().CompareI(kNeighborhoodAgeFilename) == 0) {
                            // if we get here then it's because we're linking to a neighborhood that we don't belong to
                            // and our own neighborhood book is not volatile, so really we want to basic link
                            link->SetLinkingRules(plNetCommon::LinkingRules::kBasicLink);
                            success = kLinkImmediately;
                            break;
                        }
                    }
                    linkNode->DecRef();
                }
            }

            link->SetLinkingRules(plNetCommon::LinkingRules::kOwnedBook);
            // falls thru to OWNED BOOK case...

        //--------------------------------------------------------------------
        // OWNED BOOK. Look for the book in our AgesIOwn folder
        case plNetCommon::LinkingRules::kOwnedBook:
            {
                plAgeLinkStruct ownedLink;
                if (VaultGetOwnedAgeLink(info, &ownedLink)) {
                    info->CopyFrom(ownedLink.GetAgeInfo());
                    // Remember spawn point (treasure book support)                     
                    plSpawnPointInfo theSpawnPt = link->SpawnPoint();
                    VaultAddOwnedAgeSpawnPoint(*info->GetAgeInstanceGuid(), theSpawnPt);
                }
                else {
                    success = kLinkFailed;
                }
            }
            break;

        //--------------------------------------------------------------------
        // VISIT BOOK. Look for the book in our AgesICanVisit folder
        case plNetCommon::LinkingRules::kVisitBook:
            {
                plAgeLinkStruct visitLink;
                if (VaultGetVisitAgeLink(info, &visitLink))
                    info->CopyFrom(visitLink.GetAgeInfo());
                else
                    success = kLinkFailed;
            }
            break;

        //--------------------------------------------------------------------
        // SUB-AGE BOOK: Look for an existing sub-age in this age's SubAges folder and use it if found.
        //  plNetClientTaskHandler will add a SubAge back link to our current age once we arrive there.
        case plNetCommon::LinkingRules::kSubAgeBook:
            {
                plAgeLinkStruct subAgeLink;
                if (VaultAgeFindOrCreateSubAgeLink(info, &subAgeLink, NetCommGetAge()->ageInstId))
                    info->CopyFrom(subAgeLink.GetAgeInfo());
                else
                    success = kLinkDeferred;
            }
            break;

        //--------------------------------------------------------------------
        // CHILD-AGE BOOK: Look for an existing child-age in this parent ageinfo ChildAges folder and use it if found.
        //  plNetClientTaskHandler will add a ChildAge back link to our current age once we arrive there.
        case plNetCommon::LinkingRules::kChildAgeBook:
            {
                plAgeLinkStruct childLink;
                wchar_t parentAgeName[MAX_PATH];
                if (link->HasParentAgeFilename())
                    StrToUnicode(parentAgeName, link->GetParentAgeFilename(), arrsize(parentAgeName));

                switch(VaultAgeFindOrCreateChildAgeLink(
                      (link->HasParentAgeFilename() ? parentAgeName : nil),
                      info,
                      &childLink))
                {
                    case static_cast<uint8_t>(hsFail):
                        success = kLinkFailed;
                        break;
                    case false:
                        success = kLinkDeferred;
                        break;
                    case true:
                        success = kLinkImmediately;
                }

                if (success == kLinkImmediately)
                    info->CopyFrom(childLink.GetAgeInfo());
            }
            break;

        //--------------------------------------------------------------------
        // ???
        DEFAULT_FATAL(link->GetLinkingRules());
    }

    hsLogEntry(nc->DebugMsg( "plNetLinkingMgr: Post-Process: Linking with %s rules...",
        plNetCommon::LinkingRules::LinkingRuleStr(link->GetLinkingRules())));

    hsAssert(info->HasAgeFilename(), "AgeLink has no AgeFilename. Link will fail.");

    return success;
}
Beispiel #6
0
void plNetLinkingMgr::IPostProcessLink( void )
{
    // Grab some useful things...
    plAgeLinkStruct* link = GetAgeLink();
    plAgeInfoStruct* info = link->GetAgeInfo();

    bool city = (info->GetAgeFilename().CompareI(kCityAgeFilename) == 0);
    bool hood = (info->GetAgeFilename().CompareI(kNeighborhoodAgeFilename) == 0);
    bool psnl = (info->GetAgeFilename().CompareI(kPersonalAgeFilename) == 0);

    // Update our online status 
    if (RelVaultNode* rvnInfo = VaultGetPlayerInfoNodeIncRef()) {
        VaultPlayerInfoNode accInfo(rvnInfo);
        wchar_t ageInstName[MAX_PATH];
        plUUID ageInstGuid = *info->GetAgeInstanceGuid();
        StrToUnicode(ageInstName, info->GetAgeInstanceName(), arrsize(ageInstName));
        accInfo.SetAgeInstName(ageInstName);
        accInfo.SetAgeInstUuid(ageInstGuid);
        accInfo.SetOnline(true);
        rvnInfo->DecRef();
    }
    
    switch (link->GetLinkingRules()) {

        case plNetCommon::LinkingRules::kOwnedBook: {
            // SPECIAL CASE: City: Every player ever created would be in the list; avoid that.
            if (city)
                break;
                
            {   // Ensure we're in the AgeOwners folder
                RelVaultNode* fldr = VaultGetAgeAgeOwnersFolderIncRef();
                RelVaultNode* info = VaultGetPlayerInfoNodeIncRef();
                
                if (fldr && info)
                    if (!fldr->IsParentOf(info->GetNodeId(), 1))
                        VaultAddChildNode(
                            fldr->GetNodeId(),
                            info->GetNodeId(),
                            NetCommGetPlayer()->playerInt,
                            nil,
                            nil
                        );
                
                if (fldr)
                    fldr->DecRef();
                if (info)
                    info->DecRef();
            }
        }
        break;  

        case plNetCommon::LinkingRules::kVisitBook: {
            // SPECIAL CASE: City: Every player ever created would be in the list; avoid that.
            if (city)
                break;
                
            {   // Ensure we're in the CanVisit folder
                RelVaultNode* fldr = VaultGetAgeCanVisitFolderIncRef();
                RelVaultNode* info = VaultGetPlayerInfoNodeIncRef();
                
                if (fldr && info)
                    if (!fldr->IsParentOf(info->GetNodeId(), 1))
                        VaultAddChildNode(
                            fldr->GetNodeId(),
                            info->GetNodeId(),
                            NetCommGetPlayer()->playerInt,
                            nil,
                            nil
                        );
                
                if (fldr)
                    fldr->DecRef();
                if (info)
                    info->DecRef();
            }
        }
        break;
        
        case plNetCommon::LinkingRules::kSubAgeBook: {
            // Register the previous age as a sub age of the current one so that we can link back to that instance
            plAgeLinkStruct subAgeLink;
            VaultAgeFindOrCreateSubAgeLink(GetPrevAgeLink()->GetAgeInfo(), &subAgeLink, NetCommGetAge()->ageInstId);
        }
        break;
    }
}
Beispiel #7
0
//============================================================================
bool plNCAgeJoiner::MsgReceive (plMessage * msg) {
    plNetClientMgr *    nc = plNetClientMgr::GetInstance();
    plAvatarMgr *       am = plAvatarMgr::GetInstance();
    plAgeLoader *       al = plAgeLoader::GetInstance();

    //========================================================================
    // Finished updating the age from FileSrv
    //========================================================================
    if (plResPatcherMsg * resMsg = plResPatcherMsg::ConvertNoRef(msg)) {

        if (resMsg->Success())
        {
            nc->ResetServerTimeOffset();
            NetCommLinkToAge(
                age,
                this
            );
            LogMsg(kLogPerf, L"AgeJoiner: Next:kNoOp (age updated)");
        } else
            Complete(false, resMsg->GetError());
        return true;
    }

    //========================================================================
    // Connected to age instance
    //========================================================================
    if (plNetCommLinkToAgeMsg * linkToAgeMsg = plNetCommLinkToAgeMsg::ConvertNoRef(msg)) {
        if (IS_NET_ERROR(linkToAgeMsg->result)) {
            Complete(false, "LinkToAge failed");
        }
        else if (unsigned ageVaultId = NetCommGetAge()->ageVaultId) {
            // Download the age vault
            VaultDownload(
                L"AgeJoin",
                ageVaultId,
                AgeVaultDownloadCallback,
                this,
                nil, // FVaultDownloadProgressCallback
                this
            );
        }
        else {
            // not vault to downloaded, just start loading age data
            LogMsg(kLogPerf, L"AgeJoiner: Next:kLoadAge (no vault)");
            nextOp = kLoadAge;
        }
        return true;
    }

    //========================================================================
    // All age data paged in
    //========================================================================
    if (plAgeLoaded2Msg * ageLoaded2Msg = plAgeLoaded2Msg::ConvertNoRef(msg)) {
        // Exec custom age settings
        al->ExecPendingAgeFniFiles();
        al->ExecPendingAgeCsvFiles();

        LogMsg(kLogPerf, L"AgeJoiner: Next:kLoadPlayer");
        nextOp = kLoadPlayer;
        return true;
    }

    //========================================================================
    // Local avatar loaded
    //========================================================================
    plPlayerPageMsg * playerPageMsg = plPlayerPageMsg::ConvertNoRef(msg);
    if (playerPageMsg && !playerPageMsg->fUnload && playerPageMsg->fPlayer && playerPageMsg->fLocallyOriginated) {

        if (NetCommNeedToLoadAvatar())
            NetCommSetAvatarLoaded();
        
        LogMsg(kLogPerf, L"AgeJoiner: Next:kPropagatePlayer");
        nextOp = kPropagatePlayer;
        return false;   // NetClientMgr must also handle this message
    }
    
    //========================================================================
    // Received all SDL states
    //========================================================================
    plNetClientMgrMsg * netClientMgrMsg = plNetClientMgrMsg::ConvertNoRef(msg);
    if (netClientMgrMsg && netClientMgrMsg->type == plNetClientMgrMsg::kNotifyRcvdAllSDLStates) {
        LogMsg(kLogPerf, L"AgeJoiner: Next:kEnableClickables");
        nextOp = kDestroyProgressBar;
        return true;
    }

    //========================================================================
    // Done loading all states. Time to link in!
    //========================================================================
    plInitialAgeStateLoadedMsg * stateMsg = plInitialAgeStateLoadedMsg::ConvertNoRef(msg);
    if(stateMsg) {
        plNetObjectDebugger::GetInstance()->LogMsg("OnServerInitComplete");
        nc->SetFlagsBit(plNetClientApp::kLoadingInitialAgeState, false);

        const plArmatureMod *avMod = plAvatarMgr::GetInstance()->GetLocalAvatar();

        plLinkEffectsTriggerMsg* lem = new plLinkEffectsTriggerMsg();
        lem->SetLeavingAge(false);  // linking in
        lem->SetLinkKey(nc->GetLocalPlayerKey());
        plKey animKey = avMod->GetLinkInAnimKey();
        lem->SetLinkInAnimKey(animKey);

        // indicate if we are invisible
        if (avMod && avMod->IsInStealthMode() && avMod->GetTarget(0))
            lem->SetInvisLevel(avMod->GetStealthLevel());

        lem->SetBCastFlag(plMessage::kNetPropagate);
        lem->MuteLinkSfx(muteLinkSfx);
        lem->AddReceiver(hsgResMgr::ResMgr()->FindKey(plUoid(kLinkEffectsMgr_KEY)));
        lem->AddReceiver(hsgResMgr::ResMgr()->FindKey(plUoid(kClient_KEY)));
        lem->Send();

        Complete(true, "Age joined");
        return true;
    }
    
    return false;
}
//============================================================================
bool plNCAgeJoiner::MsgReceive (plMessage * msg) {
    plNetClientMgr *    nc = plNetClientMgr::GetInstance();
    plAvatarMgr *       am = plAvatarMgr::GetInstance();
    plAgeLoader *       al = plAgeLoader::GetInstance();

    //========================================================================
    // Finished updating the age from FileSrv
    //========================================================================
    if (plResPatcherMsg * resMsg = plResPatcherMsg::ConvertNoRef(msg)) {

        if (resMsg->Success())
        {
            nc->ResetServerTimeOffset();
            NetCommLinkToAge(
                age,
                this
            );
            LogMsg(kLogPerf, L"AgeJoiner: Next:kNoOp (age updated)");
        } else
            Complete(false, resMsg->GetError());
        return true;
    }

    //========================================================================
    // Connected to age instance
    //========================================================================
    if (plNetCommLinkToAgeMsg * linkToAgeMsg = plNetCommLinkToAgeMsg::ConvertNoRef(msg)) {
        if (IS_NET_ERROR(linkToAgeMsg->result)) {
            Complete(false, "LinkToAge failed");
        }
        else if (unsigned ageVaultId = NetCommGetAge()->ageVaultId) {
            // Download the age vault
            VaultDownload(
                L"AgeJoin",
                ageVaultId,
                AgeVaultDownloadCallback,
                this,
                nil, // FVaultDownloadProgressCallback
                this
            );
        }
        else {
            // not vault to downloaded, just start loading age data
            LogMsg(kLogPerf, L"AgeJoiner: Next:kLoadAge (no vault)");
            nextOp = kLoadAge;
        }
        return true;
    }

    //========================================================================
    // All age data paged in
    //========================================================================
    if (plAgeLoaded2Msg * ageLoaded2Msg = plAgeLoaded2Msg::ConvertNoRef(msg)) {
        // Exec custom age settings
        al->ExecPendingAgeFniFiles();
        al->ExecPendingAgeCsvFiles();

        LogMsg(kLogPerf, L"AgeJoiner: Next:kLoadPlayer");
        nextOp = kLoadPlayer;
        return true;
    }

    //========================================================================
    // Local avatar loaded
    //========================================================================
    plPlayerPageMsg * playerPageMsg = plPlayerPageMsg::ConvertNoRef(msg);
    if (playerPageMsg && !playerPageMsg->fUnload && playerPageMsg->fPlayer && playerPageMsg->fLocallyOriginated) {

        if (NetCommNeedToLoadAvatar())
            NetCommSetAvatarLoaded();
        
        LogMsg(kLogPerf, L"AgeJoiner: Next:kPropagatePlayer");
        nextOp = kPropagatePlayer;
        return false;   // NetClientMgr must also handle this message
    }
    
    //========================================================================
    // Received all SDL states
    //========================================================================
    plNetClientMgrMsg * netClientMgrMsg = plNetClientMgrMsg::ConvertNoRef(msg);
    if (netClientMgrMsg && netClientMgrMsg->type == plNetClientMgrMsg::kNotifyRcvdAllSDLStates) {
        LogMsg(kLogPerf, L"AgeJoiner: Next:kEnableClickables");
        nextOp = kDestroyProgressBar;
        return true;
    }
    
    return false;
}
//============================================================================
void plNCAgeLeaver::ExecNextOp () {
    plNetClientMgr *    nc = plNetClientMgr::GetInstance();
    plAvatarMgr *       am = plAvatarMgr::GetInstance();
    plAgeLoader *       al = plAgeLoader::GetInstance();

    NextOp next = nextOp;
    nextOp      = kNoOp;
    switch (next) {
        //====================================================================
        case kNoOp: {
        }
        break;

        //====================================================================
        case kDisableClickables: {
            (new plInputIfaceMgrMsg(plInputIfaceMgrMsg::kDisableClickables))->Send();
            nextOp = kLinkOutFX;
        }
        break;

        //====================================================================
        case kLinkOutFX: {
            nc->StartLinkOutFX();
        }
        break;

        //====================================================================
        case kUnloadAge: {
            NetCliGameDisconnect();

            // Cull nodes that were part of this age vault (but not shared by the player's vault)
            VaultCull(NetCommGetAge()->ageVaultId);

            // remove the age device inbox mappings
            VaultClearDeviceInboxMap();
            
            // Tell our local player that he's unspawning (if that is indeed the case)
            nc->IPlayerChangeAge(true /* exiting */, 0/* respawn */);
            // disconnect age vault
            
            // @@@ TODO: Unload age vault here

            plAgeLoader::GetInstance()->UnloadAge();            // unload age
            nc->ISendCameraReset(false/*leaving age*/);         // reset camera
            nc->IUnloadRemotePlayers();                         // unload other players
            nc->IUnloadNPCs();                                  // unload non-player clones

            if (NetCommNeedToLoadAvatar())
                am->UnLoadLocalPlayer();
        }
        break;

        //====================================================================
        case kNotifyAgeUnloaded: {
            // Set "Playing Game" bit to false
            nc->SetFlagsBit(plNetClientMgr::kPlayingGame, false);
            
            // Release AgeSDL object, if any
            if (nc->fAgeSDLObjectKey)
                nc->GetKey()->Release(nc->fAgeSDLObjectKey);
                
            // All done leaving age
            Complete(true, "Age unloaded");
        }
        break;

        DEFAULT_FATAL(nextOp);
    }
}