/*==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 .
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==*/
#include "plCreatableIndex.h"
#include "hsTimer.h"
#include "hsStream.h"
#include "plNetClientMgr.h"
#include "plgDispatch.h"
#include "plPhysical.h"
#include "plNetClientMsgHandler.h"
#include "plNetLinkingMgr.h"
#include "plNetObjectDebugger.h"
#include "pnUtils/pnUtils.h"
#include "pnProduct/pnProduct.h"
#include "pnNetCommon/plSynchedObject.h"
#include "pnNetCommon/plNetServers.h"
#include "pnNetCommon/plSDLTypes.h"
#include "pnKeyedObject/plKey.h"
#include "pnKeyedObject/plFixedKey.h"
#include "pnKeyedObject/hsKeyedObject.h"
#include "pnSceneObject/plSceneObject.h"
#include "pnSceneObject/plObjInterface.h"
#include "pnSceneObject/plCoordinateInterface.h"
#include "pnMessage/plClientMsg.h"
#include "pnMessage/plSDLModifierMsg.h"
#include "pnMessage/plPlayerPageMsg.h"
#include "pnMessage/plTimeMsg.h"
#include "pnModifier/plModifier.h"
#include "pnAsyncCore/pnAsyncCore.h"
#include "plAgeLoader/plAgeLoader.h"
#include "plAgeLoader/plResPatcher.h"
#include "plNetClientRecorder/plNetClientRecorder.h"
#include "plScene/plSceneNode.h"
#include "plNetCommon/plNetCommonConstants.h"
#include "plNetMessage/plNetMessage.h"
#include "plMessage/plLoadAvatarMsg.h"
#include "plMessage/plLoadCloneMsg.h"
#include "plMessage/plSynchEnableMsg.h"
#include "plMessage/plLinkToAgeMsg.h"
#include "plMessage/plLoadAgeMsg.h"
#include "plMessage/plAgeLoadedMsg.h"
#include "plMessage/plCCRMsg.h"
#include "plMessage/plAvatarMsg.h"
#include "plMessage/plNetVoiceListMsg.h"
#include "plMessage/plNetCommMsgs.h"
#include "plMessage/plNetClientMgrMsg.h"
#include "plResMgr/plKeyFinder.h"
#include "plResMgr/plPageInfo.h"
#include "plNetTransport/plNetTransportMember.h"
#include "plAgeDescription/plAgeDescription.h"
#include "plAvatar/plAvatarClothing.h"
#include "plAvatar/plArmatureMod.h"
#include "plAvatar/plAvatarMgr.h"
#include "plSurface/plLayerInterface.h"
#include "plStatusLog/plStatusLog.h"
#include "plSDL/plSDL.h"
#include "plUnifiedTime/plUnifiedTime.h"
#include "plFile/plEncryptedStream.h"
#include "plProgressMgr/plProgressMgr.h"
#include "plVault/plVault.h"
#include "pfMessage/pfKIMsg.h" // Move this to PubUtil level
#if 1 // for debugging
#include "plCreatableIndex.h"
#include "plModifier/plResponderModifier.h"
#include "plSurface/plLayerAnimation.h"
#endif
#include
#include
////////////////////////////////////////////////////////////////////
plNetClientMgr::PendingLoad::~PendingLoad()
{
delete fSDRec;
}
////////////////////////////////////////////////////////////////////
//
// CONSTRUCT
//
#pragma warning(disable:4355) // this used in initializer list
plNetClientMgr::plNetClientMgr() :
fLocalPlayerKey(nil),
fMsgHandler(this),
fJoinOrder(0),
// fProgressBar( nil ),
fTaskProgBar( nil ),
fMsgRecorder(nil),
fServerTimeOffset(0),
fTimeSamples(0),
fLastTimeUpdate(0),
fListenListMode(kListenList_Distance),
fAgeSDLObjectKey(nil),
fExperimentalLevel(0),
fOverrideAgeTimeOfDayPercent(-1.f),
fNumInitialSDLStates(0),
fRequiredNumInitialSDLStates(0),
fDisableMsg(nil),
fIsOwner(true)
{
#ifndef HS_DEBUGGING
// release code will timeout inactive players on servers by default
SetFlagsBit(kAllowTimeOut);
#endif
SetFlagsBit(kAllowAuthTimeOut);
// fPlayerVault.SetPlayerName("SinglePlayer"); // in a MP game, this will be replaced with a player name like 'Atrus'
fTransport.SetNumChannels(kNetNumChannels);
}
#pragma warning(default:4355)
//
// DESTRUCT
//
plNetClientMgr::~plNetClientMgr()
{
plSynchedObject::PushSynchDisabled(true); // disable dirty tracking
IDeInitNetClientComm();
if (this==GetInstance())
SetInstance(nil); // we're going down boys
IClearPendingLoads();
}
//
// App is exiting
//
void plNetClientMgr::Shutdown()
{
ISendDirtyState(hsTimer::GetSysSeconds());
plNetLinkingMgr::GetInstance()->LeaveAge(true);
// release existing remote players
int i;
for (i=0;iUnLoadRemotePlayer(k);
}
// Finally, pump the dispatch system so all the new refs get delivered.
plgDispatch::Dispatch()->MsgQueueProcess();
if (fMsgRecorder)
{
delete fMsgRecorder;
fMsgRecorder = nil;
}
for (i = 0; i < fMsgPlayers.size(); i++)
delete fMsgPlayers[i];
fMsgPlayers.clear();
IRemoveCloneRoom();
// RATHER BAD DEBUG HACK: Clear the spawn override in armatureMod so there's no memory leak
plArmatureMod::SetSpawnPointOverride( nil );
VaultDestroy();
}
//
// Clear any pending state, probably joining a new age
//
void plNetClientMgr::IClearPendingLoads()
{
std::for_each( fPendingLoads.begin(), fPendingLoads.end(), xtl::delete_ptr() );
fPendingLoads.clear();
}
//
// manually add/remove the cloned object room in plClient
//
void plNetClientMgr::IAddCloneRoom()
{
plSceneNode *cloneRoom = TRACKED_NEW plSceneNode();
cloneRoom->RegisterAs(kNetClientCloneRoom_KEY);
plKey clientKey=hsgResMgr::ResMgr()->FindKey(kClient_KEY);
plClientRefMsg *pMsg1 = TRACKED_NEW plClientRefMsg(clientKey, plRefMsg::kOnCreate, -1, plClientRefMsg::kManualRoom);
hsgResMgr::ResMgr()->AddViaNotify(cloneRoom->GetKey(), pMsg1, plRefFlags::kPassiveRef);
}
//
// manually add/remove the cloned object room in plClient
//
void plNetClientMgr::IRemoveCloneRoom()
{
plKey cloneRoomKey = hsgResMgr::ResMgr()->FindKey(kNetClientCloneRoom_KEY);
plSceneNode *cloneRoom = cloneRoomKey ? plSceneNode::ConvertNoRef(cloneRoomKey->ObjectIsLoaded()) : nil;
if (cloneRoom)
{
cloneRoom->UnRegisterAs(kNetClientCloneRoom_KEY);
cloneRoom = nil;
}
}
//
// turn null send on/off. Null send does everything except actually send the msg out on the socket
//
void plNetClientMgr::SetNullSend(hsBool on)
{
}
//
// returns server time in the form "[m/d/y h:m:s]"
//
const char* plNetClientMgr::GetServerLogTimeAsString(std::string& timestamp) const
{
const plUnifiedTime st=GetServerTime();
xtl::format(timestamp, "{%02d/%02d %02d:%02d:%02d}",
st.GetMonth(), st.GetDay(), st.GetHour(), st.GetMinute(), st.GetSecond());
return timestamp.c_str();
}
//
// support for a single leading tab in statusLog strings
//
const char* ProcessTab(const char* fmt)
{
static std::string s;
if (fmt && *fmt=='\t')
{
s = xtl::format(" %s", fmt);
return s.c_str();
}
return fmt;
}
//
// override for plLoggable
//
bool plNetClientMgr::Log(const char* str) const
{
if (strlen(str)==nil)
return true;
// prepend raw time
std::string buf2 = xtl::format("%.2f %s", hsTimer::GetSeconds(), ProcessTab(str));
if ( GetConsoleOutput() )
hsStatusMessage(buf2.c_str());
// create status log if necessary
if(fStatusLog==nil)
{
fStatusLog = plStatusLogMgr::GetInstance().CreateStatusLog(40, "network.log",
plStatusLog::kTimestamp | plStatusLog::kFilledBackground | plStatusLog::kAlignToTop |
plStatusLog::kServerTimestamp);
fWeCreatedLog = true;
}
plNetObjectDebugger::GetInstance()->LogMsgIfMatch(buf2.c_str());
return fStatusLog->AddLine(buf2.c_str());
}
//
// Display OS version info for log
//
void plNetClientMgr::IDumpOSVersionInfo() const
{
DebugMsg("*** OS Info");
char** versionStrs = DisplaySystemVersion();
int i=0;
while(versionStrs && versionStrs[i])
{
DebugMsg(versionStrs[i]);
delete [] versionStrs[i];
i++;
}
delete [] versionStrs;
}
//
// initialize net client. returns hsFail on err.
//
int plNetClientMgr::Init()
{
int ret=hsOK;
hsLogEntry( DebugMsg("*** plNetClientMgr::Init GMT:%s", plUnifiedTime::GetCurrentTime().Print()) );
IDumpOSVersionInfo();
if (GetFlagsBit(kNullSend))
SetNullSend(true);
VaultInitialize();
RegisterAs( kNetClientMgr_KEY );
IAddCloneRoom();
fNetGroups.Reset();
plgDispatch::Dispatch()->RegisterForExactType(plNetClientMgrMsg::Index(), GetKey());
plgDispatch::Dispatch()->RegisterForExactType(plAgeLoadedMsg::Index(), GetKey());
plgDispatch::Dispatch()->RegisterForExactType(plAgeLoaded2Msg::Index(), GetKey());
plgDispatch::Dispatch()->RegisterForExactType(plCCRPetitionMsg::Index(), GetKey());
plgDispatch::Dispatch()->RegisterForExactType(plPlayerPageMsg::Index(), GetKey());
plgDispatch::Dispatch()->RegisterForExactType(plInitialAgeStateLoadedMsg::Index(), GetKey());
plgDispatch::Dispatch()->RegisterForExactType(plNetVoiceListMsg::Index(), GetKey());
plgDispatch::Dispatch()->RegisterForExactType(plClientMsg::Index(), GetKey());
plgDispatch::Dispatch()->RegisterForExactType(plEvalMsg::Index(), GetKey());
plgDispatch::Dispatch()->RegisterForExactType(plNetCommAuthMsg::Index(), GetKey());
plgDispatch::Dispatch()->RegisterForExactType(plNetCommActivePlayerMsg::Index(), GetKey());
plgDispatch::Dispatch()->RegisterForExactType(plNetCommLinkToAgeMsg::Index(), GetKey());
IInitNetClientComm();
return ret;
}
//
// Prepare to send.
// Update p2p transport groups and rcvrs list in some msgs
// Returns channel.
//
int plNetClientMgr::IPrepMsg(plNetMessage* msg)
{
// pick channel, prepare msg
int channel=kNetChanDefault;
plNetMsgVoice* v=plNetMsgVoice::ConvertNoRef(msg);
if (v)
{ // VOICE MSG
channel=kNetChanVoice;
// compute new transport group (from talkList) if necessary
GetTalkList()->UpdateTransportGroup(this);
// update receivers list in voice msg based on talk list
v->Receivers()->Clear();
int i;
for(i=0;iGetNumMembers(); i++)
{
v->Receivers()->AddReceiverPlayerID(GetTalkList()->GetMember(i)->GetPlayerID());
}
if (msg->IsBitSet(plNetMessage::kEchoBackToSender))
v->Receivers()->AddReceiverPlayerID(GetPlayerID());
}
else if (plNetMsgListenListUpdate::ConvertNoRef(msg))
{ // LISTEN LIST UPDATE MSG
channel=kNetChanListenListUpdate;
// update transport group from rcvrs list, add p2p mbrs to trasnport group
fTransport.ClearChannelGrp(kNetChanListenListUpdate);
if (IsPeerToPeer())
{
plNetMsgReceiversListHelper* rl = plNetMsgReceiversListHelper::ConvertNoRef(msg);
hsAssert(rl, "error converting msg to rcvrs list?");
int i;
for(i=0;iGetNumReceivers();i++)
{
int idx=fTransport.FindMember(rl->GetReceiverPlayerID(i));
hsAssert(idx!=-1, "error finding transport mbr");
plNetTransportMember* tm = fTransport.GetMember(idx);
if (tm->IsPeerToPeer())
fTransport.SubscribeToChannelGrp(tm, kNetChanListenListUpdate);
}
}
}
else if( plNetMsgGameMessageDirected::ConvertNoRef( msg ) )
{
channel = kNetChanDirectedMsg;
}
return channel;
}
//
// unload other player since we're leaving the age
//
void plNetClientMgr::IUnloadRemotePlayers()
{
for(int i=RemotePlayerKeys().size()-1;i>=0;i--)
plAvatarMgr::GetInstance()->UnLoadRemotePlayer(RemotePlayerKeys()[i]);
hsAssert(!RemotePlayerKeys().size(),"Still remote players left when linking out");
}
//
// begin linking out sounds and gfx
//
void plNetClientMgr::StartLinkOutFX()
{
// send msg to trigger link out effect
if (fLocalPlayerKey)
{
plNetLinkingMgr * lm = plNetLinkingMgr::GetInstance();
plLinkEffectsTriggerMsg* lem = TRACKED_NEW plLinkEffectsTriggerMsg();
lem->SetLeavingAge(true);
lem->SetLinkKey(fLocalPlayerKey);
lem->SetBCastFlag(plMessage::kNetPropagate);
lem->SetBCastFlag(plMessage::kNetForce); // Necessary?
lem->AddReceiver(hsgResMgr::ResMgr()->FindKey(plUoid(kLinkEffectsMgr_KEY)));
lem->Send();
}
}
//
// beging linking in sounds snd gfx
//
void plNetClientMgr::StartLinkInFX()
{
if (fLocalPlayerKey)
{
const plArmatureMod *avMod = plAvatarMgr::GetInstance()->GetLocalAvatar();
plLinkEffectsTriggerMsg* lem = TRACKED_NEW plLinkEffectsTriggerMsg();
lem->SetLeavingAge(false); // linking in
lem->SetLinkKey(fLocalPlayerKey);
lem->SetLinkInAnimKey(avMod->GetLinkInAnimKey());
// indicate if we are invisible
if (avMod && avMod->IsInStealthMode() && avMod->GetTarget(0))
{
lem->SetInvisLevel(avMod->GetStealthLevel());
}
lem->SetBCastFlag(plMessage::kNetPropagate);
lem->AddReceiver(hsgResMgr::ResMgr()->FindKey(plUoid(kLinkEffectsMgr_KEY)));
lem->AddReceiver(hsgResMgr::ResMgr()->FindKey(plUoid(kClient_KEY)));
plgDispatch::MsgSend(lem);
}
}
//
// compute the difference between our clock and the server's in unified time
//
void plNetClientMgr::UpdateServerTimeOffset(plNetMessage* msg)
{
if ((hsTimer::GetSysSeconds() - fLastTimeUpdate) > 5)
{
fLastTimeUpdate = hsTimer::GetSysSeconds();
const plUnifiedTime& msgSentUT = msg->GetTimeSent();
if (!msgSentUT.AtEpoch())
{
double diff = plUnifiedTime::GetTimeDifference(msgSentUT, plClientUnifiedTime::GetCurrentTime());
if (fServerTimeOffset == 0)
{
fServerTimeOffset = diff;
}
else
{
fServerTimeOffset = fServerTimeOffset + ((diff - fServerTimeOffset) / ++fTimeSamples);
}
DebugMsg("Setting server time offset to %f", fServerTimeOffset);
}
}
}
void plNetClientMgr::ResetServerTimeOffset()
{
fServerTimeOffset = 0;
fTimeSamples = 0;
fLastTimeUpdate = 0;
}
//
// return the gameservers time
//
plUnifiedTime plNetClientMgr::GetServerTime() const
{
if ( fServerTimeOffset==0 ) // offline mode or before connecting/calibrating to a server
return plUnifiedTime::GetCurrentTime();
plUnifiedTime serverUT;
if (fServerTimeOffset<0)
return plUnifiedTime::GetCurrentTime() - plUnifiedTime(fabs(fServerTimeOffset));
else
return plUnifiedTime::GetCurrentTime() + plUnifiedTime(fServerTimeOffset);
}
//
// How old is the age
//
double plNetClientMgr::GetCurrentAgeElapsedSeconds() const
{
return plAgeLoader::GetInstance()->GetCurrAgeDesc().GetAgeElapsedSeconds(GetServerTime());
}
//
// Get the time in the current age (0-1), taking into account the age's day length
//
float plNetClientMgr::GetCurrentAgeTimeOfDayPercent() const
{
if (fOverrideAgeTimeOfDayPercent>=0)
return fOverrideAgeTimeOfDayPercent;
return plAgeLoader::GetInstance()->GetCurrAgeDesc().GetAgeTimeOfDayPercent(GetServerTime());
}
//
// main update fxn for net client code, return hsFail on err
//
int plNetClientMgr::Update(double secs)
{
int ret=hsOK; // ret code is unchecked, but what the hay
if (GetFlagsBit(kDisableOnNextUpdate)) {
SetFlagsBit(kDisableOnNextUpdate, false);
IDisableNet();
}
// Pump net messages
NetCommUpdate();
static double lastUpdateTime=0;
double curTime=hsTimer::GetSeconds();
if (curTime-lastUpdateTime > 1.f)
{
DebugMsg("NetClient hasn't updated for %f secs", curTime-lastUpdateTime);
}
lastUpdateTime=curTime;
if (!GetFlagsBit(kDisabled))
{
fNetClientStats.UpdateAgeStats();
}
if (GetFlagsBit(kPlayingGame) )
{
MaybeSendPendingPagingRoomMsgs();
ICheckPendingStateLoad(secs);
ISendDirtyState(secs);
IUpdateListenList(secs);
if (GetFlagsBit(plNetClientApp::kShowLists))
IShowLists();
if (GetFlagsBit(plNetClientApp::kShowRooms))
IShowRooms();
if (GetFlagsBit(plNetClientApp::kShowAvatars))
IShowAvatars();
if (GetFlagsBit(plNetClientApp::kShowRelevanceRegions))
IShowRelevanceRegions();
}
// Send dirty nodes, deliver vault callbacks, etc.
VaultUpdate();
plNetLinkingMgr::GetInstance()->Update();
return ret;
}
//
// See if there is state recvd from the network that needsto be delivered
//
void plNetClientMgr::ICheckPendingStateLoad(double secs)
{
if (!fPendingLoads.empty() && GetFlagsBit( kPlayingGame ) || (GetFlagsBit(kLoadingInitialAgeState) && !GetFlagsBit(kNeedInitialAgeStateCount)))
{
PendingLoadsList::iterator it = fPendingLoads.begin();
while ( it!=fPendingLoads.end() )
{
PendingLoad * pl = (*it);
// cache rcvr key
if (!pl->fKey)
{
// check for existence of key in dataset, excluding clone info.
plUoid tmpUoid = pl->fUoid;
tmpUoid.SetClone(0,0);
if ( !hsgResMgr::ResMgr()->FindKey( tmpUoid ) )
{
// discard the state if object not found in dataset.
char tmp[256];
hsLogEntry( DebugMsg( "Failed to find object %s in dataset. Discarding pending state '%s'",
tmpUoid.StringIze(tmp),
pl->fSDRec->GetDescriptor()->GetName() ) );
delete pl;
it = fPendingLoads.erase(it);
continue;
}
// find and cache the real key.
pl->fKey = hsgResMgr::ResMgr()->FindKey(pl->fUoid);
}
// deliver state if possible
plSynchedObject*so = pl->fKey ? plSynchedObject::ConvertNoRef(pl->fKey->ObjectIsLoaded()) : nil;
if (so && so->IsFinal())
{
plSDLModifierMsg* sdlMsg = TRACKED_NEW plSDLModifierMsg(pl->fSDRec->GetDescriptor()->GetName(),
plSDLModifierMsg::kRecv);
sdlMsg->SetState( pl->fSDRec, true/*delete pl->fSDRec for us*/ );
sdlMsg->SetPlayerID( pl->fPlayerID );
#ifdef HS_DEBUGGING
if (plNetObjectDebugger::GetInstance()->IsDebugObject(so))
{
hsLogEntry( DebugMsg( "Delivering SDL state %s:%s", pl->fKey->GetName(), pl->fSDRec->GetDescriptor()->GetName() ) );
// hsLogEntry(plNetObjectDebugger::GetInstance()->LogMsg(xtl::format("Dispatching SDL state, type %s to object:%s, locallyOwned=%d, st=%.3f rt=%.3f",
// pl->fSDRec->GetDescriptor()->GetName(), pl->fKey->GetName(),
// so->IsLocallyOwned()==plSynchedObject::kYes, secs, hsTimer::GetSeconds()).c_str()));
// hsLogEntry( pl->fSDRec->DumpToObjectDebugger( "Delivering SDL state", false, 0 ) );
}
#endif
sdlMsg->Send(pl->fKey);
pl->fSDRec = nil; // so it won't be deleted in the PendingLoad dtor.
delete pl;
it = fPendingLoads.erase(it);
}
else
{
// report old pending state
double rawSecs = hsTimer::GetSeconds();
if ((rawSecs - pl->fQueuedTime) > 60.f /*secs*/)
{
char tmp[256], tmp2[256];
if (pl->fQueueTimeResets >= 5)
{
// if this is our fifth time in here then we've been queued
// for around 5 minutes and its time to go
WarningMsg( "Pending state '%s' for object [uoid:%s,key:%s] has been queued for about %f secs. Removing...",
pl->fSDRec && pl->fSDRec->GetDescriptor() ? pl->fSDRec->GetDescriptor()->GetName() : "?",
pl->fUoid.StringIze(tmp2), pl->fKey ? pl->fKey->GetUoid().StringIze(tmp) : "?",
( rawSecs - pl->fQueuedTime ) * pl->fQueueTimeResets);
delete pl;
it = fPendingLoads.erase(it);
continue;
}
WarningMsg( "Pending state '%s' for object [uoid:%s,key:%s] has been queued for about %f secs. %s",
pl->fSDRec && pl->fSDRec->GetDescriptor() ? pl->fSDRec->GetDescriptor()->GetName() : "?",
pl->fUoid.StringIze(tmp2), pl->fKey ? pl->fKey->GetUoid().StringIze(tmp) : "?",
( rawSecs - pl->fQueuedTime ) * pl->fQueueTimeResets,
so ? "(not loaded)" : "(not final)" );
// reset queue time so we don't spew too many log msgs.
pl->fQueuedTime = rawSecs;
pl->fQueueTimeResets += 1;
}
it++;
}
}
}
}
//
// Determine the net group for a given object (based on paging unit)
//
plNetGroupId plNetClientMgr::SelectNetGroup(plSynchedObject* objIn, plKey roomKey)
{
if( objIn->GetSynchFlags() & plSynchedObject::kHasConstantNetGroup )
return objIn->GetNetGroup();
return plNetGroupId(roomKey->GetUoid().GetLocation(), 0);
}
//
// Get the net group that an object belongs to, taking into acct
// that objects may derive their net group from another object they belong to.
//
plNetGroupId plNetClientMgr::GetEffectiveNetGroup(const plSynchedObject*& obj) const
{
plNetGroupId netGroup = plNetGroup::kNetGroupUnknown;
// the object is an interface, so consider the owner instead
const plObjInterface* oInt=plObjInterface::ConvertNoRef(obj);
if (oInt)
{
// the object is an interface, so consider the owner instead
if (oInt->GetOwner())
obj = oInt->GetOwner();
}
else
{
const plModifier* mod=plModifier::ConvertNoRef(obj);
if (mod)
{
// the object is a modifier, so consider the first target instead
if (mod->GetNumTargets() && mod->GetTarget(0))
obj = mod->GetTarget(0);
}
else
{
const plLayerInterface* li = plLayerInterface::ConvertNoRef(obj);
const plClothingOutfit* cl = plClothingOutfit::ConvertNoRef(obj);
if (li || cl)
{
// the object is a layer interface or clothing object
plUoid u = obj->GetKey()->GetUoid();
if (u.GetLocation().IsReserved())
{
// layer interface is associated with an avatar, find which one
int cloneID = u.GetCloneID();
int clonePlayerID = u.GetClonePlayerID();
for(int i = -1; i < RemotePlayerKeys().size(); i++)
{
plKey pKey = (i==-1) ? GetLocalPlayerKey() : RemotePlayerKeys()[i];
if (pKey && pKey->GetUoid().GetCloneID()==cloneID && pKey->GetUoid().GetClonePlayerID()==clonePlayerID)
{
obj = plSynchedObject::ConvertNoRef(pKey->ObjectIsLoaded());
break;
}
}
}
else
{
// layer interface is on a non-avatar object
netGroup = u.GetLocation();
}
}
else
{
const plPhysical* ph = plPhysical::ConvertNoRef(obj);
if (ph)
{
// the object is a physical
obj = plSynchedObject::ConvertNoRef(ph->GetObjectKey()->ObjectIsLoaded());
}
}
}
}
if (obj)
netGroup = obj->GetNetGroup();
return netGroup;
}
//
// If we don't know for sure if something is locallyOwned, check if it' related to the remote or
// local avatar. if not, fallback to join order.
//
int plNetClientMgr::IDeduceLocallyOwned(const plUoid& uoid) const
{
// check against local/remote avatars
if (fLocalPlayerKey)
if (uoid.GetLocation() == fLocalPlayerKey->GetUoid().GetLocation() &&
uoid.GetClonePlayerID() == fLocalPlayerKey->GetUoid().GetClonePlayerID())
return plSynchedObject::kYes; // this object's location is the same as the local player's
if (!fRemotePlayerKeys.empty())
if (uoid.GetLocation() == fRemotePlayerKeys.back()->GetUoid().GetLocation() &&
uoid.GetClonePlayerID() == fRemotePlayerKeys.back()->GetUoid().GetClonePlayerID())
return plSynchedObject::kNo; // this object's location is the same as the currently loading remote player's
return fIsOwner;
}
//
// returns yes/no
// for special cases, like sceneNodes.
//
int plNetClientMgr::IsLocallyOwned(const plUoid& uoid) const
{
// we're non-networked
if ( GetFlagsBit(kDisabled))
return plSynchedObject::kYes;
plNetGroupId netGroup = uoid.GetLocation();
int answer=GetNetGroups()->IsGroupLocal(netGroup);
if (answer==-1)
answer = IDeduceLocallyOwned(uoid);
hsAssert(answer==plSynchedObject::kYes || answer==plSynchedObject::kNo, "invalid answer to IsLocallyOwned()");
return answer;
}
//
// check if this object is in a group which is owned (mastered) by this client.
// returns yes/no
//
int plNetClientMgr::IsLocallyOwned(const plSynchedObject* obj) const
{
// we're non-networked
if ( GetFlagsBit(kDisabled) )
return plSynchedObject::kYes;
if( !obj )
return plSynchedObject::kNo;
// object is flagged as local only
if (!obj->IsNetSynched())
return plSynchedObject::kYes;
plNetGroupId netGroup = GetEffectiveNetGroup(obj);
int answer=GetNetGroups()->IsGroupLocal(netGroup);
if (answer==-1) // not sure
answer = IDeduceLocallyOwned(obj->GetKey()->GetUoid());
hsAssert(answer==plSynchedObject::kYes || answer==plSynchedObject::kNo, "invalid answer to IsLocallyOwned()");
return answer;
}
//
// return localPlayer ptr
//
plSynchedObject* plNetClientMgr::GetLocalPlayer(hsBool forceLoad) const
{
if (forceLoad)
return fLocalPlayerKey ? plSynchedObject::ConvertNoRef(fLocalPlayerKey->GetObjectPtr()) : nil;
else
return fLocalPlayerKey ?
plSynchedObject::ConvertNoRef(fLocalPlayerKey->ObjectIsLoaded()) : nil;
}
//
// return a ptr to a remote player
//
plSynchedObject* plNetClientMgr::GetRemotePlayer(int i) const
{
return fRemotePlayerKeys[i] ? plSynchedObject::ConvertNoRef(fRemotePlayerKeys[i]->ObjectIsLoaded()) : nil;
}
//
// check if a key si a remote player
//
hsBool plNetClientMgr::IsRemotePlayerKey(const plKey pKey, int *idx)
{
if (pKey)
{
plKeyVec::iterator result=std::find(fRemotePlayerKeys.begin(), fRemotePlayerKeys.end(), pKey);
hsBool found = result!=fRemotePlayerKeys.end();
if (idx)
*idx = found ? result-fRemotePlayerKeys.begin() : -1;
return found;
}
return false;
}
//
// add remote char if not already there
//
void plNetClientMgr::AddRemotePlayerKey(plKey pKey)
{
hsAssert(pKey, "adding nil remote player key");
if (!IsRemotePlayerKey(pKey))
{
fRemotePlayerKeys.push_back(pKey);
hsAssert(pKey!=fLocalPlayerKey, "setting remote player to the local player?");
}
}
//
// MsgReceive handler for plasma messages
//
hsBool plNetClientMgr::MsgReceive( plMessage* msg )
{
if (plNetLinkingMgr::GetInstance()->MsgReceive( msg ))
return true;
plEvalMsg* evalMsg = plEvalMsg::ConvertNoRef(msg);
if (evalMsg)
{
IPlaybackMsgs();
if ( GetFlagsBit( kNeedToSendAgeLoadedMsg ) )
{
SetFlagsBit( kNeedToSendAgeLoadedMsg, false );
plAgeLoader::GetInstance()->NotifyAgeLoaded( true );
}
if ( GetFlagsBit( kNeedToSendInitialAgeStateLoadedMsg ) )
{
SetFlagsBit(kNeedToSendInitialAgeStateLoadedMsg, false);
plInitialAgeStateLoadedMsg* m = TRACKED_NEW plInitialAgeStateLoadedMsg;
m->Send();
}
return true;
}
plGenRefMsg* ref = plGenRefMsg::ConvertNoRef(msg);
if (ref)
{
if( ref->fType == kVaultImage )
{
// Ignore, we just use it for reffing, don't care about the actual pointer
return true;
}
hsAssert(ref->fType==kAgeSDLHook, "unknown ref msg context");
if (ref->GetContext()==plRefMsg::kOnCreate)
{
hsAssert(fAgeSDLObjectKey==nil, "already have a ref to age sdl hook");
fAgeSDLObjectKey = ref->GetRef()->GetKey();
char tmp[256];
DebugMsg("Age SDL hook object created, uoid=%s", fAgeSDLObjectKey->GetUoid().StringIze(tmp));
}
else
{
fAgeSDLObjectKey=nil;
DebugMsg("Age SDL hook object destroyed");
}
return true;
}
if (plNetClientMgrMsg * ncmMsg = plNetClientMgrMsg::ConvertNoRef(msg)) {
if (ncmMsg->type == plNetClientMgrMsg::kCmdDisableNet) {
SetFlagsBit(kDisableOnNextUpdate);
hsRefCnt_SafeUnRef(fDisableMsg);
fDisableMsg = ncmMsg;
fDisableMsg->Ref();
}
return true;
}
if (plNetCommAuthMsg * authMsg = plNetCommAuthMsg::ConvertNoRef(msg)) {
if (IS_NET_ERROR(authMsg->result)) {
char str[256];
StrPrintf(str, arrsize(str), "Authentication failed: %S", NetErrorToString(authMsg->result));
QueueDisableNet(true, str);
return false; // @@@ TODO: Handle this failure better
}
return true;
}
if (plNetCommActivePlayerMsg * activePlrMsg = plNetCommActivePlayerMsg::ConvertNoRef(msg)) {
if (IS_NET_ERROR(activePlrMsg->result)) {
char str[256];
StrPrintf(str, arrsize(str), "SetActivePlayer failed: %S", NetErrorToString(activePlrMsg->result));
QueueDisableNet(true, str);
return false; // @@@ TODO: Handle this failure better.
}
return true;
}
plPlayerPageMsg *playerPageMsg = plPlayerPageMsg::ConvertNoRef(msg);
if(playerPageMsg)
{
IHandlePlayerPageMsg(playerPageMsg);
return true; // handled
}
plLoadCloneMsg* pCloneMsg = plLoadCloneMsg::ConvertNoRef(msg);
if(pCloneMsg)
{
ILoadClone(pCloneMsg);
return true; // handled
}
// player is petitioning a CCR
plCCRPetitionMsg* petMsg=plCCRPetitionMsg::ConvertNoRef(msg);
if (petMsg)
{
ISendCCRPetition(petMsg);
return true;
}
// a remote CCR is turning invisible
plCCRInvisibleMsg* invisMsg=plCCRInvisibleMsg::ConvertNoRef(msg);
if (invisMsg)
{
LogMsg(kLogDebug, "plNetClientMgr::MsgReceive - Got plCCRInvisibleMsg");
MakeCCRInvisible(invisMsg->fAvKey, invisMsg->fInvisLevel);
return true;
}
plCCRBanLinkingMsg* banLinking = plCCRBanLinkingMsg::ConvertNoRef(msg);
if (banLinking)
{
DebugMsg("Setting BanLinking to %d", banLinking->fBan);
SetFlagsBit(kBanLinking, banLinking->fBan);
return true;
}
plCCRSilencePlayerMsg* silence = plCCRSilencePlayerMsg::ConvertNoRef(msg);
if (silence)
{
DebugMsg("Setting Silence to %d", silence->fSilence);
SetFlagsBit(kSilencePlayer, silence->fSilence);
return true;
}
plInitialAgeStateLoadedMsg *stateMsg = plInitialAgeStateLoadedMsg::ConvertNoRef( msg );
if( stateMsg != nil )
{
// done receiving the initial state of the age from the server
plNetObjectDebugger::GetInstance()->LogMsg("OnServerInitComplete");
// delete fProgressBar;
// fProgressBar=nil;
SetFlagsBit(kLoadingInitialAgeState, false);
StartLinkInFX();
}
plNetVoiceListMsg* voxList = plNetVoiceListMsg::ConvertNoRef(msg);
if (voxList)
{
IHandleNetVoiceListMsg(voxList);
return true;
}
plSynchEnableMsg* synchEnable = plSynchEnableMsg::ConvertNoRef(msg);
if (synchEnable)
{
if (synchEnable->fPush)
{
plSynchedObject::PushSynchDisabled(!synchEnable->fEnable);
}
else
{
plSynchedObject::PopSynchDisabled();
}
return true;
}
plClientMsg* clientMsg = plClientMsg::ConvertNoRef(msg);
if (clientMsg && clientMsg->GetClientMsgFlag()==plClientMsg::kInitComplete)
{
// add 1 debug object for age sdl
if (plNetObjectDebugger::GetInstance())
{
plNetObjectDebugger::GetInstance()->RemoveDebugObject("AgeSDLHook");
plNetObjectDebugger::GetInstance()->AddDebugObject("AgeSDLHook");
}
// if we're linking to startup we don't need (or want) a player set
char ageName[kMaxAgeNameLength];
StrCopy(ageName, NetCommGetStartupAge()->ageDatasetName, arrsize(ageName));
if (!StrLen(ageName))
StrCopy(ageName, "StartUp", arrsize(ageName));
if (0 == StrCmpI(ageName, "StartUp"))
NetCommSetActivePlayer(0, nil);
plAgeLinkStruct link;
link.GetAgeInfo()->SetAgeFilename(NetCommGetStartupAge()->ageDatasetName);
link.SetLinkingRules(plNetCommon::LinkingRules::kOriginalBook);
plNetLinkingMgr::GetInstance()->LinkToAge(&link);
return true;
}
return plNetClientApp::MsgReceive(msg);
}
void plNetClientMgr::IncNumInitialSDLStates()
{
fNumInitialSDLStates++;
DebugMsg( "Received %d initial SDL states", fNumInitialSDLStates );
if ( GetFlagsBit( plNetClientApp::kNeedInitialAgeStateCount ) )
{
DebugMsg( "Need initial SDL state count" );
return;
}
if ( GetNumInitialSDLStates()>=GetRequiredNumInitialSDLStates() )
{
ICheckPendingStateLoad(hsTimer::GetSysSeconds());
NotifyRcvdAllSDLStates();
}
}
void plNetClientMgr::NotifyRcvdAllSDLStates() {
DebugMsg( "Got all initial SDL states" );
plNetClientMgrMsg * msg = TRACKED_NEW plNetClientMgrMsg();
msg->type = plNetClientMgrMsg::kNotifyRcvdAllSDLStates;
msg->SetBCastFlag(plMessage::kBCastByType);
msg->Send();
}
//
// return true if we should send this msg
//
bool plNetClientMgr::CanSendMsg(plNetMessage * msg)
{
// TEMP
if (GetFlagsBit(kSilencePlayer))
{
if (plNetMsgVoice::ConvertNoRef(msg))
return false;
plNetMsgGameMessage* gm=plNetMsgGameMessage::ConvertNoRef(msg);
if (gm && gm->StreamInfo()->GetStreamType()==pfKIMsg::Index())
{
hsReadOnlyStream stream(gm->StreamInfo()->GetStreamLen(), gm->StreamInfo()->GetStreamBuf());
pfKIMsg* kiMsg = pfKIMsg::ConvertNoRef(hsgResMgr::ResMgr()->ReadCreatable(&stream));
if (kiMsg && kiMsg->GetCommand()==pfKIMsg::kHACKChatMsg)
{
delete kiMsg;
return false;
}
delete kiMsg;
}
}
return true;
}
//
// Return the net client (account) name of the player whose avatar
// key is provided. If avKey is nil, returns local client name.
//
const char* plNetClientMgr::GetPlayerName(const plKey avKey) const
{
// local case
if (!avKey || avKey==GetLocalPlayerKey())
return NetCommGetPlayer()->playerNameAnsi;
plNetTransportMember* mbr=TransportMgr().GetMember(TransportMgr().FindMember(avKey));
return mbr ? mbr->GetPlayerName() : nil;
}
const char * plNetClientMgr::GetPlayerNameById (unsigned playerId) const {
// local case
if (NetCommGetPlayer()->playerInt == playerId)
return NetCommGetPlayer()->playerNameAnsi;
plNetTransportMember * mbr = TransportMgr().GetMember(TransportMgr().FindMember(playerId));
return mbr ? mbr->GetPlayerName() : nil;
}
unsigned plNetClientMgr::GetPlayerIdByName (const char name[]) const {
// local case
if (0 == StrCmp(name, NetCommGetPlayer()->playerNameAnsi, (unsigned)-1))
NetCommGetPlayer()->playerInt;
unsigned n = TransportMgr().GetNumMembers();
for (unsigned i = 0; i < n; ++i)
if (plNetTransportMember * member = TransportMgr().GetMember(i))
if (0 == StrCmp(name, member->GetPlayerName(), (unsigned)-1))
return member->GetPlayerID();
return 0;
}
UInt32 plNetClientMgr::GetPlayerID() const
{
return NetCommGetPlayer()->playerInt;
}
//
// return true if the age is the set of local ages
//
static const char* gLocalAges[]={"GUI", "AvatarCustomization", ""};
static bool IsLocalAge(const char* ageName)
{
int i=0;
while(strlen(gLocalAges[i]))
{
if (!stricmp(gLocalAges[i], ageName))
return true;
i++;
}
return false;
}
//
// is the object in a local age?
//
bool plNetClientMgr::ObjectInLocalAge(const plSynchedObject* obj) const
{
plLocation loc = obj->GetKey()->GetUoid().GetLocation();
const plPageInfo* pageInfo = plKeyFinder::Instance().GetLocationInfo(loc);
bool ret= IsLocalAge(pageInfo->GetAge());
return ret;
}
//
// the next age we are going to
//
const char* plNetClientMgr::GetNextAgeFilename()
{
// set when we start linking to an age.
plNetLinkingMgr * lm = plNetLinkingMgr::GetInstance();
return lm->GetAgeLink()->GetAgeInfo()->GetAgeFilename();
}
//
// a remote ccr is turning [in]visible
//
void plNetClientMgr::MakeCCRInvisible(plKey avKey, int level)
{
if (!avKey)
{
ErrorMsg("Failed to make remote CCR Invisible, nil avatar key");
return;
}
if (level<0)
{
ErrorMsg("Failed to make remote CCR Invisible, negative level");
return;
}
if (!avKey->ObjectIsLoaded() || !avKey->ObjectIsLoaded()->IsFinal())
{
hsAssert(false, "avatar not loaded or final");
return;
}
plAvatarStealthModeMsg *msg = TRACKED_NEW plAvatarStealthModeMsg();
msg->SetSender(avKey);
msg->fLevel = level;
if (GetCCRLevel()fMode = plAvatarStealthModeMsg::kStealthCloaked;
else
{
// he's visible to me
if (AmCCR() && level > 0)
msg->fMode = plAvatarStealthModeMsg::kStealthCloakedButSeen; // draw as semi-invis
else
msg->fMode = plAvatarStealthModeMsg::kStealthVisible;
}
DebugMsg("Handled MakeCCRInvisible - sending stealth msg");;
// This fxn is called when avatar SDL state is received from the server,
// so this msg will inherit the 'non-local' status of the parent sdl msg.
// That means that the avatar linkSound won't receive it, since the linkSound
// is set as localOnly.
// So, terminate the remote cascade and start a new (local) cascade.
msg->SetBCastFlag(plMessage::kNetStartCascade);
msg->Send();
}
void plNetClientMgr::QueueDisableNet (bool showDlg, const char str[]) {
plNetClientMgrMsg * msg = NEWZERO(plNetClientMgrMsg);
msg->type = plNetClientMgrMsg::kCmdDisableNet;
msg->yes = showDlg;
if (str)
StrCopy(msg->str, str, arrsize(msg->str));
#if 0
msg->Send(GetKey());
#else
msg->Ref();
fDisableMsg = msg;
IDisableNet();
#endif;
}
void plNetClientMgr::IDisableNet () {
ASSERT(fDisableMsg);
if (!GetFlagsBit(kDisabled)) {
SetFlagsBit(kDisabled);
// cause subsequent net operations to fail immediately, but don't block
// waiting for net subsystem to shutdown (we'll do that later)
NetCommEnableNet(false, false);
// display a msg to the player
if ( fDisableMsg->yes )
{
if (!GetFlagsBit(plNetClientApp::kPlayingGame))
{
// KI may not be loaded
char title[256];
StrPrintf(title, arrsize(title), "%S Error", ProductCoreName());
hsMessageBox(fDisableMsg->str, title, hsMessageBoxNormal, hsMessageBoxIconError );
plClientMsg *quitMsg = NEW(plClientMsg)(plClientMsg::kQuit);
quitMsg->Send(hsgResMgr::ResMgr()->FindKey(kClient_KEY));
}
else
{
pfKIMsg *msg = TRACKED_NEW pfKIMsg(pfKIMsg::kKIOKDialog);
msg->SetString(fDisableMsg->str);
msg->Send();
}
}
}
hsRefCnt_SafeUnRef(fDisableMsg);
fDisableMsg = nil;
}
bool plNetClientMgr::IHandlePlayerPageMsg(plPlayerPageMsg *playerMsg)
{
bool result = false;
plKey playerKey = playerMsg->fPlayer;
int idx;
if(playerMsg->fUnload)
{
if (GetLocalPlayerKey() == playerKey)
{
fLocalPlayerKey = nil;
DebugMsg("Net: Unloading local player %s", playerKey->GetName());
// notify server - NOTE: he might not still be around to get this...
plNetMsgPlayerPage npp (playerKey->GetUoid(), playerMsg->fUnload);
npp.SetNetProtocol(kNetProtocolCli2Game);
SendMsg(&npp);
}
else if (IsRemotePlayerKey(playerKey, &idx))
{
fRemotePlayerKeys.erase(fRemotePlayerKeys.begin()+idx); // remove key from list
DebugMsg("Net: Unloading remote player %s", playerKey->GetName());
}
}
else
{
plSceneObject *playerSO = plSceneObject::ConvertNoRef(playerKey->ObjectIsLoaded());
if (!playerSO)
{
hsStatusMessageF("Ignoring player page message for non-existant player.");
}
else
if(playerMsg->fPlayer)
{
if (playerMsg->fLocallyOriginated)
{
hsAssert(!GetLocalPlayerKey() ||
GetLocalPlayerKey() == playerKey,
"Different local player already loaded");
hsLogEntry(DebugMsg("Adding LOCAL player %s\n", playerKey->GetName()));
playerSO->SetNetGroupConstant(plNetGroup::kNetGroupLocalPlayer);
// don't save avatar state permanently on server
playerSO->SetSynchFlagsBit(plSynchedObject::kAllStateIsVolatile);
const plCoordinateInterface* co = playerSO->GetCoordinateInterface();
if (co)
{
int i;
for(i=0;iGetNumChildren();i++)
{
if (co->GetChild(i) && co->GetChild(i)->GetOwner())
const_cast(co->GetChild(i)->GetOwner())->SetSynchFlagsBit(plSynchedObject::kAllStateIsVolatile);
}
}
// notify server
plNetMsgPlayerPage npp (playerKey->GetUoid(), playerMsg->fUnload);
npp.SetNetProtocol(kNetProtocolCli2Game);
SendMsg(&npp);
}
else
{
hsLogEntry(DebugMsg("Adding REMOTE player %s\n", playerKey->GetName()));
playerSO->SetNetGroupConstant(plNetGroup::kNetGroupRemotePlayer);
idx=fTransport.FindMember(playerMsg->fClientID);
if( idx != -1 )
{
hsAssert(playerKey, "NIL KEY?");
hsAssert(playerKey->GetName(), "UNNAMED KEY");
fTransport.GetMember(idx)->SetAvatarKey(playerKey);
}
else
{
hsLogEntry(DebugMsg("Ignoring player page msg (player not found in member list) : %s\n", playerKey->GetName()));
}
}
hsAssert(IFindModifier(playerSO, CLASS_INDEX_SCOPED(plAvatarSDLModifier)), "avatar missing avatar SDL modifier");
hsAssert(IFindModifier(playerSO, CLASS_INDEX_SCOPED(plClothingSDLModifier)), "avatar missing clothing SDL modifier");
hsAssert(IFindModifier(playerSO, CLASS_INDEX_SCOPED(plAGMasterSDLModifier)), "avatar missing AGMaster SDL modifier");
result = true;
}
}
return result;
}
// for debugging purposes
bool plNetClientMgr::IFindModifier(plSynchedObject* obj, Int16 classIdx)
{
plLayerAnimation* layer = plLayerAnimation::ConvertNoRef(obj);
if (layer)
return (layer->GetSDLModifier() != nil);
plResponderModifier* resp = plResponderModifier::ConvertNoRef(obj);
if (resp)
return (resp->GetSDLModifier() != nil);
int cnt=0;
plSceneObject* sceneObj=plSceneObject::ConvertNoRef(obj);
if (sceneObj)
{
int i;
for(i=0; iGetNumModifiers(); i++)
if (sceneObj->GetModifier(i) && sceneObj->GetModifier(i)->ClassIndex()==classIdx)
cnt++;
}
hsAssert(cnt<2, xtl::format("Object %s has multiple SDL modifiers of the same kind (%s)?",
obj->GetKeyName(), plFactory::GetNameOfClass(classIdx)).c_str());
return cnt==0 ? false : true;
}
plUoid plNetClientMgr::GetAgeSDLObjectUoid(const char* ageName) const
{
hsAssert(ageName, "nil ageName");
// if age sdl hook is loaded
if (fAgeSDLObjectKey)
return fAgeSDLObjectKey->GetUoid();
// if age is loaded
plLocation loc = plKeyFinder::Instance().FindLocation(ageName,plAgeDescription::GetCommonPage(plAgeDescription::kGlobal));
if (!loc.IsValid())
{
// check current age des
if (plAgeLoader::GetInstance()->GetCurrAgeDesc().GetAgeName() &&
!strcmp(plAgeLoader::GetInstance()->GetCurrAgeDesc().GetAgeName(), ageName))
loc=plAgeLoader::GetInstance()->GetCurrAgeDesc().CalcPageLocation("BuiltIn");
if (!loc.IsValid())
{
// try to load age desc
hsStream* stream=plAgeLoader::GetAgeDescFileStream(ageName);
if (stream)
{
plAgeDescription ad;
ad.Read(stream);
loc=ad.CalcPageLocation("BuiltIn");
stream->Close();
}
delete stream;
}
}
return plUoid(loc, plSceneObject::Index(), plSDL::kAgeSDLObjectName);
}
//
// Add a state update to the pending queue
//
void plNetClientMgr::AddPendingLoad(PendingLoad *pl)
{
pl->fQueuedTime = hsTimer::GetSeconds(); // timestamp
// find corresponding key
pl->fKey = hsgResMgr::ResMgr()->FindKey(pl->fUoid);
// check for age SDL state
char tmp[256];
if (pl->fUoid.GetObjectName() && !strcmp(pl->fUoid.GetObjectName(), plSDL::kAgeSDLObjectName))
{
DebugMsg("Recv SDL state for age hook object, uoid=%s", pl->fUoid.StringIze(tmp));
if (!pl->fKey)
WarningMsg("Can't find age hook object, nil key!");
else
DebugMsg("Found age hook object");
}
// check if object is ready
if (pl->fKey)
{
if (!pl->fKey->ObjectIsLoaded())
{
WarningMsg("Object %s not loaded, withholding SDL state",
pl->fKey->GetUoid().StringIze(tmp));
}
else if (!pl->fKey->ObjectIsLoaded()->IsFinal())
{
WarningMsg("Object %s is not FINAL, withholding SDL state",
pl->fKey->GetUoid().StringIze(tmp));
}
}
// add entry
fPendingLoads.push_back(pl);
}
void plNetClientMgr::AddPendingPagingRoomMsg( plNetMsgPagingRoom * msg )
{
fPendingPagingRoomMsgs.push_back( msg );
}
void plNetClientMgr::MaybeSendPendingPagingRoomMsgs()
{
if ( GetFlagsBit( kPlayingGame ) )
SendPendingPagingRoomMsgs();
}
void plNetClientMgr::SendPendingPagingRoomMsgs()
{
for ( int i=0; i