/*==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 "hsTypes.h"
#include "pnKeyedObject/plKey.h"
#include "hsTemplates.h"
#include "hsStream.h"
#include "plLinkEffectsMgr.h"
#include "pnMessage/plEventCallbackMsg.h"
#include "pnMessage/plTimeMsg.h"
#include "pnMessage/plPlayerPageMsg.h"
#include "plMessage/plLinkToAgeMsg.h"
#include "plMessage/plTransitionMsg.h"
#include "plgDispatch.h"
#include "hsResMgr.h"
#include "hsTimer.h"
#include "pnNetCommon/plNetApp.h"
#include "plNetClient/plNetClientMgr.h"
#include "pnSceneObject/plSceneObject.h"
#include "plNetTransport/plNetTransportMember.h"
#include "plVault/plVault.h"
#include "plNetClient/plNetLinkingMgr.h"
#include "plAgeLoader/plAgeLoader.h"
#include "pnSceneObject/plCoordinateInterface.h"
#include "pnMessage/plWarpMsg.h"
#include "pnKeyedObject/plFixedKey.h"
// chronicle var
#define kCleftSolved L"CleftSolved"
#include "plAvatar/plArmatureMod.h"
#include "plAvatar/plAvatarTasks.h"
#include "plAvatar/plAGAnim.h"
#include "plMessage/plAvatarMsg.h"
#include "plMessage/plLoadAgeMsg.h"
plLinkEffectsMgr::plLinkEffectsMgr()
{
}
plLinkEffectsMgr::~plLinkEffectsMgr()
{
int i;
for( i = 0; i < fLinks.GetCount(); i++ )
{
hsRefCnt_SafeUnRef(fLinks[i]);
}
for( i = 0; i < fWaitlist.GetCount(); i++ )
{
hsRefCnt_SafeUnRef(fWaitlist[i]);
}
for( i = 0; i < fDeadlist.GetCount(); i++ )
{
hsRefCnt_SafeUnRef(fDeadlist[i]);
}
}
void plLinkEffectsMgr::Init()
{
plgDispatch::Dispatch()->RegisterForExactType(plPlayerPageMsg::Index(), GetKey());
plgDispatch::Dispatch()->RegisterForExactType(plPseudoLinkEffectMsg::Index(), GetKey());
}
plLinkEffectsTriggerMsg *plLinkEffectsMgr::IFindLinkTriggerMsg(plKey linkKey)
{
int i;
for (i = 0; i < fLinks.GetCount(); i++)
{
if (fLinks[i]->GetLinkKey() == linkKey)
return fLinks[i];
}
return nil;
}
void plLinkEffectsMgr::IAddLink(plLinkEffectsTriggerMsg *msg)
{
hsRefCnt_SafeRef(msg);
fLinks.Append(msg);
}
void plLinkEffectsMgr::IAddWait(plLinkEffectsTriggerMsg *msg)
{
hsRefCnt_SafeRef(msg);
fWaitlist.Append(msg);
}
void plLinkEffectsMgr::IAddDead(plLinkEffectsTriggerMsg *msg)
{
hsRefCnt_SafeRef(msg);
fDeadlist.Append(msg);
}
void plLinkEffectsMgr::IAddPsuedo(plPseudoLinkEffectMsg *msg)
{
hsRefCnt_SafeRef(msg);
fPseudolist.Append(msg);
}
hsBool plLinkEffectsMgr::IHuntWaitlist(plLinkEffectsTriggerMsg *msg)
{
int i;
hsBool found = false;
for (i = fWaitlist.GetCount() - 1; i >= 0; i--)
{
if (fWaitlist[i] == msg)
{
found = true;
hsRefCnt_SafeUnRef(fWaitlist[i]);
fWaitlist.Remove(i);
plNetApp::GetInstance()->DebugMsg("Received backup LinkEffectsTriggerMsg. Never got remote trigger!\n");
}
}
return found || IHuntWaitlist(msg->GetLinkKey());
}
hsBool plLinkEffectsMgr::IHuntWaitlist(plKey linkKey)
{
int i;
hsBool found = false;
for (i = fWaitlist.GetCount() - 1; i >= 0; i--)
{
if (fWaitlist[i]->GetLinkKey() == linkKey)
{
found = true;
IAddDead(fWaitlist[i]);
hsRefCnt_SafeUnRef(fWaitlist[i]);
fWaitlist.Remove(i);
plNetApp::GetInstance()->DebugMsg("Received remote LinkEffectsTriggerMsg. Awaiting backup.\n");
}
}
return found;
}
hsBool plLinkEffectsMgr::IHuntDeadlist(plLinkEffectsTriggerMsg *msg)
{
int i;
hsBool found = false;
for (i = fDeadlist.GetCount() - 1; i >= 0; i--)
{
if (fDeadlist[i] == msg)
{
found = true;
hsRefCnt_SafeUnRef(fDeadlist[i]);
fDeadlist.Remove(i);
plNetApp::GetInstance()->DebugMsg("Received backup LinkEffectsTriggerMsg. Cleanly ignoring since we received remote trigger.\n");
}
}
return found;
}
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 = TRACKED_NEW plLinkOutUnloadMsg; // derived from LoadAgeMsg
lam->SetAgeFilename( NetCommGetAge()->ageDatasetName );
lam->AddReceiver(plNetClientMgr::GetInstance()->GetKey());
lam->SetPlayerID(plNetClientMgr::GetInstance()->GetPlayerID());
lam->Send();
}
}
else
{
plLinkInDoneMsg* lid = TRACKED_NEW plLinkInDoneMsg;
lid->AddReceiver(fLinks[i]->GetLinkKey());
lid->SetBCastFlag(plMessage::kPropagateToModifiers);
lid->Send();
if (fLinks[i]->GetLinkKey() == plNetClientApp::GetInstance()->GetLocalPlayerKey())
{
plLinkInDoneMsg* lid = TRACKED_NEW plLinkInDoneMsg;
lid->AddReceiver(plNetClientMgr::GetInstance()->GetKey());
lid->Send();
}
}
hsRefCnt_SafeUnRef(fLinks[i]);
fLinks.Remove(i);
hsStatusMessage("Done - removing link FX msg\n");
}
}
}
hsBool plLinkEffectsMgr::MsgReceive(plMessage *msg)
{
plNetClientMgr* nc = plNetClientMgr::GetInstance();
plNetLinkingMgr* lm = plNetLinkingMgr::GetInstance();
plPseudoLinkEffectMsg* pSeudoMsg = plPseudoLinkEffectMsg::ConvertNoRef(msg);
if (pSeudoMsg)
{
// verify valid avatar and "link" objects
if (!pSeudoMsg->fAvatarKey)
return true;
if (!pSeudoMsg->fLinkObjKey)
return true;
if (!pSeudoMsg->fLinkObjKey->ObjectIsLoaded())
return true;
if (!plNetClientMgr::GetInstance()->IsAPlayerKey(pSeudoMsg->fAvatarKey))
return true;
// send the trigger message to the avatar...
plPseudoLinkAnimTriggerMsg* pMsg = TRACKED_NEW plPseudoLinkAnimTriggerMsg(true, pSeudoMsg->fAvatarKey);
pMsg->SetSender(GetKey());
pMsg->Send();
IAddPsuedo(pSeudoMsg);
}
plPseudoLinkAnimCallbackMsg* pSeudoCallback = plPseudoLinkAnimCallbackMsg::ConvertNoRef(msg);
if (pSeudoCallback)
{
// warp the avatar to his new position
plPseudoLinkEffectMsg* pMsg = IFindPseudo(pSeudoCallback->fAvatarKey);
if (pMsg)
{
plSceneObject* pObj = plSceneObject::ConvertNoRef(pMsg->fLinkObjKey->ObjectIsLoaded());
if (pObj && pObj->GetCoordinateInterface())
{
hsMatrix44 mat = pObj->GetCoordinateInterface()->GetLocalToWorld();
// create message
plWarpMsg* pMsg = TRACKED_NEW plWarpMsg(mat);
pMsg->SetWarpFlags(plWarpMsg::kFlushTransform);
pMsg->AddReceiver(pSeudoCallback->fAvatarKey);
plUoid U(kVirtualCamera1_KEY);
plKey pCamKey = hsgResMgr::ResMgr()->FindKey(U);
if (pCamKey)
{
pMsg->AddReceiver(pCamKey);
}
plgDispatch::MsgSend( pMsg ); // whoosh... off it goes
// now make him re-appear
plPseudoLinkAnimTriggerMsg* pTrigMsg = TRACKED_NEW plPseudoLinkAnimTriggerMsg(false, pSeudoCallback->fAvatarKey);
pTrigMsg->SetSender(GetKey());
pTrigMsg->Send();
IRemovePseudo(pSeudoCallback->fAvatarKey);
}
}
}
plLinkEffectsTriggerPrepMsg *tpMsg = plLinkEffectsTriggerPrepMsg::ConvertNoRef(msg);
if (tpMsg)
{
plNetApp::GetInstance()->DebugMsg("Received LinkEffectsTriggerPREPMsg\n");
IAddWait(tpMsg->GetTrigger());
plLinkEffectPrepBCMsg *bcpMsg = TRACKED_NEW plLinkEffectPrepBCMsg;
bcpMsg->fLeavingAge = tpMsg->fLeavingAge;
bcpMsg->fLinkKey = tpMsg->fLinkKey;
bcpMsg->Send();
return true;
}
plLinkEffectsTriggerMsg* pTriggerMsg = plLinkEffectsTriggerMsg::ConvertNoRef(msg);
if (pTriggerMsg)
{
plNetApp::GetInstance()->DebugMsg("Received LinkEffectsTriggerMsg, local=%d, linkingIn=%d, stealth=%d",
!msg->HasBCastFlag(plMessage::kNetNonLocal),
!pTriggerMsg->IsLeavingAge(), pTriggerMsg->GetInvisLevel());
plKey linkKey = pTriggerMsg->GetLinkKey();
if (linkKey == nil)
return true;
if ((linkKey != nc->GetLocalPlayerKey()) &&
(!pTriggerMsg->IsLeavingAge()))
{
if (IHuntDeadlist(pTriggerMsg)) // Just an obselete safety trigger
return true;
if (!IHuntWaitlist(pTriggerMsg))
{
plNetApp::GetInstance()->DebugMsg("Unexpected linkEffectsTriggerMsg. Ignoring\n");
return true;
}
}
plSceneObject *avatar = plSceneObject::ConvertNoRef(linkKey->ObjectIsLoaded());
if (avatar == nil)
{
plNetApp::GetInstance()->DebugMsg("Can't find avatar, mod=%s\n", linkKey->GetName());
return true;
}
// This is not the right place to catch this problem.
// if (IFindLinkTriggerMsg(linkKey) != nil)
// {
// hsAssert(false, "Trying to link an Avatar already in the process of linking.");
// return true;
// }
if (pTriggerMsg->GetInvisLevel() && linkKey != nc->GetLocalPlayerKey())
{
#ifdef PLASMA_EXTERNAL_RELEASE
// Verify that the server told us that the invisible avatar is a CCR
plNetTransportMember* mbr=nc->TransportMgr().GetMember(nc->TransportMgr().FindMember(linkKey));
if (!mbr || mbr->GetCCRLevel()GetInvisLevel())
{
plNetApp::StaticErrorMsg("Remote Avatar trying to be stealthy - REJECTING since he's not a CCR");
}
else
#endif
{
plNetApp::StaticDebugMsg("Remote Avatar is in stealth mode - making invisible");
nc->MakeCCRInvisible(pTriggerMsg->GetLinkKey(), pTriggerMsg->GetInvisLevel());
}
}
if (pTriggerMsg->IsLeavingAge())
hsStatusMessage("Starting LinkOut FX\n");
else
hsStatusMessage("Starting LinkIn FX\n");
plLinkEffectBCMsg *BCMsg = TRACKED_NEW plLinkEffectBCMsg();
BCMsg->fLinkKey = linkKey;
BCMsg->SetLinkFlag(plLinkEffectBCMsg::kLeavingAge, pTriggerMsg->IsLeavingAge());
BCMsg->SetLinkFlag(plLinkEffectBCMsg::kSendCallback, true);
// Check if you have a Yeesha book, and mute sound if you don't.
// 'CleftSolved' gets set when you click on the linking panel in the cleft,
// so we use that instead of checking KILevel.
// Also, check if you're going to/from the ACA, or through the fissure, and mute sound if you are.
if (linkKey == nc->GetLocalPlayerKey())
{
if(lm) {
const char *ageName = lm->GetAgeLink()->GetAgeInfo()->GetAgeFilename();
const char *prevAgeName = lm->GetPrevAgeLink()->GetAgeInfo()->GetAgeFilename();
bool linkToStartup = ageName && !stricmp(ageName, kStartUpAgeFilename ); // To Startup
bool linkFromStartup = prevAgeName && !stricmp(prevAgeName, kStartUpAgeFilename); // Leaving Startup
bool cleftSolved = VaultHasChronicleEntry( kCleftSolved );
bool linkToACA = ageName && !stricmp(ageName, kAvCustomizationFilename);
bool linkFromACA = prevAgeName && !stricmp(prevAgeName, kAvCustomizationFilename);
bool linkToFissureDrop = lm &&
lm->GetAgeLink()->HasSpawnPt() &&
lm->GetAgeLink()->SpawnPoint().GetName() &&
!stricmp(lm->GetAgeLink()->SpawnPoint().GetName(), kCleftAgeLinkInPointFissureDrop);
bool linkToDsntFromShell = lm &&
lm->GetAgeLink()->HasSpawnPt() &&
lm->GetAgeLink()->SpawnPoint().GetTitle() &&
!stricmp(lm->GetAgeLink()->SpawnPoint().GetTitle(), kDescentLinkFromShell);
if ( linkToACA || linkFromACA || linkToStartup || linkFromStartup || linkToFissureDrop || linkToDsntFromShell)
{
BCMsg->SetLinkFlag(plLinkEffectBCMsg::kMute);
}
}
}
BCMsg->SetSender(GetKey());
if (msg->HasBCastFlag(plMessage::kNetNonLocal))
// terminate the remote cascade and start a new (local) cascade, since the rcvr is localOnly and will reject remote msgs
BCMsg->SetBCastFlag(plMessage::kNetStartCascade);
plgDispatch::MsgSend(BCMsg);
if (!pTriggerMsg->IsLeavingAge()) // Avatar is currently entering a new age
{
plATCAnim *linkInAnim = nil;
plKey linkInAnimKey = nil;
const plArmatureMod *avMod = plArmatureMod::ConvertNoRef(avatar->GetModifierByType(plArmatureMod::Index()));
if (pTriggerMsg->HasBCastFlag(plMessage::kNetNonLocal))
{
// Remote trigger, they should tell us how they linked in.
linkInAnimKey = pTriggerMsg->GetLinkInAnimKey();
}
else
{
// this is our backup trigger we send ourselves. We've already received the remote player's SDL.
linkInAnimKey = avMod ? avMod->GetLinkInAnimKey() : nil;
}
linkInAnim = plATCAnim::ConvertNoRef(linkInAnimKey ? linkInAnimKey->ObjectIsLoaded() : nil);
if (avMod && linkInAnim)
{
plAvOneShotTask *task = TRACKED_NEW plAvOneShotTask(linkInAnim->GetName(), false, false, nil);
task->fBackwards = true;
task->fDisableLooping = true;
task->fDisablePhysics = false;
(TRACKED_NEW plAvTaskMsg(GetKey(), avMod->GetKey(), task))->Send();
}
}
IAddLink(pTriggerMsg); // refs the avatarMod
// Dummy msg sent after the broadcast. This guarantees we have a callback to actually trigger the
// link, plus we know any effect broadcast messages will have processed before this (and therefore
// have told us to wait for them.)
pTriggerMsg->fEffects++;
plLinkCallbackMsg *dummyMsg = TRACKED_NEW plLinkCallbackMsg();
dummyMsg->AddReceiver(GetKey());
dummyMsg->fLinkKey = linkKey;
plgDispatch::MsgSend(dummyMsg);
return true;
}
// callbacks from linkout events
plLinkCallbackMsg* pLinkCallbackMsg = plLinkCallbackMsg::ConvertNoRef(msg);
if (pLinkCallbackMsg)
{
plNetApp::GetInstance()->DebugMsg("Received pLinkCallbackMsg, localmsg=%d\n",
!msg->HasBCastFlag(plMessage::kNetNonLocal));
static char str[ 128 ];
plLinkEffectsTriggerMsg *pTriggerMsg = IFindLinkTriggerMsg(pLinkCallbackMsg->fLinkKey);
if (pTriggerMsg == nil)
{
hsAssert(true, "Received a callback for an avatar that isn't linking.");
return true;
}
if (--pTriggerMsg->fEffects == 0)
{
plNetApp::GetInstance()->DebugMsg("All link callbacks received.\n" );
plgDispatch::Dispatch()->RegisterForExactType(plTimeMsg::Index(), GetKey());
}
else if (pTriggerMsg->fEffects < 0 )
{
plNetApp::GetInstance()->DebugMsg("Too many link callbacks received for avatar %s. Ignoring extras.\n",
pTriggerMsg->GetLinkKey()->GetName());
}
else
{
plNetApp::GetInstance()->DebugMsg("%d link callbacks left until avatar %s links...\n",
pTriggerMsg->fEffects, pTriggerMsg->GetLinkKey()->GetName());
}
return true;
}
plTimeMsg *time = plTimeMsg::ConvertNoRef(msg);
if (time) // This is how we know we're out of the render function, and it's safe to pageIn/Out nodes
{
plgDispatch::Dispatch()->UnRegisterForExactType(plTimeMsg::Index(), GetKey());
ISendAllReadyCallbacks();
return true;
}
plPlayerPageMsg *pageMsg = plPlayerPageMsg::ConvertNoRef(msg);
if (pageMsg)
{
if (pageMsg->fUnload)
{
IHuntWaitlist(pageMsg->fPlayer);
return true;
}
const hsScalar kMaxTimeForLinkTrigger = 30.f;
// If we're not loading state, we're in the age. So this avatar coming in must be linking in.
// If the player is us, no prep is necessary.
if (!plNetClientApp::GetInstance()->IsLoadingInitialAgeState() &&
(pageMsg->fPlayer != nc->GetLocalPlayerKey()))
{
plLinkEffectsTriggerMsg *trigMsg = TRACKED_NEW plLinkEffectsTriggerMsg;
trigMsg->SetLeavingAge(false);
trigMsg->SetLinkKey(pageMsg->fPlayer);
// Send off the prep message right away
plLinkEffectsTriggerPrepMsg *trigPrepMsg = TRACKED_NEW plLinkEffectsTriggerPrepMsg;
trigPrepMsg->fLinkKey = pageMsg->fPlayer;
trigPrepMsg->SetTrigger(trigMsg);
trigPrepMsg->Send(GetKey());
// Send off a delayed safety trigger. If things are going along properly,
// we'll get a trigger from the player linking in before this message is
// received, and we'll ignore it.
double timeToDeliver = hsTimer::GetSysSeconds() + kMaxTimeForLinkTrigger;
trigMsg->SetTimeStamp(timeToDeliver);
trigMsg->Send(GetKey());
}
return true;
}
return hsKeyedObject::MsgReceive(msg);
}
void plLinkEffectsMgr::WaitForEffect(plKey linkKey, hsScalar time)
{
plLinkEffectsTriggerMsg *msg = IFindLinkTriggerMsg(linkKey);
if (msg == nil)
{
hsAssert(true, "Request to wait on an effect for an avatar that isn't linking.");
return;
}
msg->fEffects++;
plLinkCallbackMsg *callback = TRACKED_NEW plLinkCallbackMsg();
callback->fEvent = kStop;
callback->fRepeats = 0;
callback->fLinkKey = linkKey;
double timeToDeliver = hsTimer::GetSysSeconds() + time;
callback->SetTimeStamp( timeToDeliver );
callback->Send( GetKey() );
}
plMessage *plLinkEffectsMgr::WaitForEffect(plKey linkKey)
{
plLinkEffectsTriggerMsg *msg = IFindLinkTriggerMsg(linkKey);
if (msg == nil)
{
hsAssert(true, "Request to wait on an effect for an avatar that isn't linking.");
return nil;
}
msg->fEffects++;
plLinkCallbackMsg *callback = TRACKED_NEW plLinkCallbackMsg();
callback->fEvent = kStop;
callback->fRepeats = 0;
callback->fLinkKey = linkKey;
callback->AddReceiver( GetKey() );
return callback;
}
void plLinkEffectsMgr::WaitForPseudoEffect(plKey linkKey, hsScalar time)
{
plPseudoLinkEffectMsg* msg = IFindPseudo(linkKey);
if (msg == nil)
{
hsAssert(true, "Request to wait on an fake effect for an avatar that isn't fake linking.");
return;
}
plPseudoLinkAnimCallbackMsg* callback = TRACKED_NEW plPseudoLinkAnimCallbackMsg();
callback->fAvatarKey = linkKey;
double timeToDeliver = hsTimer::GetSysSeconds() + time;
callback->SetTimeStamp( timeToDeliver );
callback->Send( GetKey() );
}
plPseudoLinkEffectMsg* plLinkEffectsMgr::IFindPseudo(plKey avatarKey)
{
int i;
for (i = 0; i < fPseudolist.GetCount(); i++)
{
if (fPseudolist[i]->fAvatarKey == avatarKey)
return fPseudolist[i];
}
return nil;
}
void plLinkEffectsMgr::IRemovePseudo(plKey avatarKey)
{
int i;
for (i = 0; i < fPseudolist.GetCount(); i++)
{
if (fPseudolist[i]->fAvatarKey == avatarKey)
{
fPseudolist.Remove(i);
return;
}
}
}