/*==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; } } }