/*==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 . Additional permissions under GNU GPL version 3 section 7 If you modify this Program, or any covered work, by linking or combining it with any of RAD Game Tools Bink SDK, Autodesk 3ds Max SDK, NVIDIA PhysX SDK, Microsoft DirectX SDK, OpenSSL library, Independent JPEG Group JPEG library, Microsoft Windows Media SDK, or Apple QuickTime SDK (or a modified version of those libraries), containing parts covered by the terms of the Bink SDK EULA, 3ds Max EULA, PhysX SDK EULA, DirectX SDK EULA, OpenSSL and SSLeay licenses, IJG JPEG Library README, Windows Media SDK EULA, or QuickTime SDK EULA, the licensors of this Program grant you additional permission to convey the resulting work. Corresponding Source for a non-source form of such a combination shall include the source code for the parts of OpenSSL and IJG JPEG Library used as well as that of the covered work. 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==*/ ////////////////////////////////////////////////////////////////////////////// // // // plSceneInputInterface // // // ////////////////////////////////////////////////////////////////////////////// #include "HeadSpin.h" #include "plSceneInputInterface.h" #include "plInputInterfaceMgr.h" #include "plInputManager.h" #include "plInputDevice.h" #include "plPhysical/plPickingDetector.h" #include "plMessage/plInputEventMsg.h" #include "plMessage/plLOSRequestMsg.h" #include "plMessage/plLOSHitMsg.h" #include "plMessage/plPickedMsg.h" #include "plMessage/plRenderMsg.h" #include "plMessage/plInputIfaceMgrMsg.h" #include "plMessage/plVaultNotifyMsg.h" #include "pnMessage/plFakeOutMsg.h" #include "pnMessage/plNotifyMsg.h" #include "pnMessage/plRemoteAvatarInfoMsg.h" #include "pnMessage/plCursorChangeMsg.h" #include "pnMessage/plCameraMsg.h" #include "pnMessage/plPlayerPageMsg.h" #include "pnMessage/plCmdIfaceModMsg.h" #include "plAvatar/plArmatureMod.h" #include "plAvatar/plAvBrain.h" #include "plAvatar/plAvatarMgr.h" #include "plAvatar/plPhysicalControllerCore.h" #include "plModifier/plInterfaceInfoModifier.h" #include "pnModifier/plLogicModBase.h" #include "plVault/plVault.h" #include "plNetClient/plNetClientMgr.h" #include "plNetClient/plNetLinkingMgr.h" #include "plNetCommon/plNetServerSessionInfo.h" #include "plNetTransport/plNetTransport.h" #include "plNetTransport/plNetTransportMember.h" #include "pnSceneObject/plSceneObject.h" #include "pnSceneObject/plCoordinateInterface.h" #include "pnKeyedObject/plKey.h" #include "pnKeyedObject/plFixedKey.h" #include "pnInputCore/plKeyMap.h" #include "plPhysical.h" #include "plgDispatch.h" #include "plPipeline.h" #include "plModifier/plDetectorLog.h" #define ID_FIND_CLICKABLE 2 #define ID_FIND_LOCALPLAYER 3 #define ID_FIND_WALKABLE_GROUND 4 #define SHARE_FACING_TOLERANCE -0.70f // 45 degrees plSceneInputInterface *plSceneInputInterface::fInstance = nil; bool plSceneInputInterface::fShowLOS = false; //// Constructor/Destructor ////////////////////////////////////////////////// plSceneInputInterface::plSceneInputInterface() { fPipe = nil; fSpawnPoint = nil; fAgeInstanceGuid.Clear(); fInstance = this; SetEnabled( true ); // Always enabled } plSceneInputInterface::~plSceneInputInterface() { ClearClickableMap(); fIgnoredAvatars.Reset(); fLocalIgnoredAvatars.Reset(); fGUIIgnoredAvatars.Reset(); fInstance = nil; } //// Init/Shutdown /////////////////////////////////////////////////////////// void plSceneInputInterface::Init( plInputInterfaceMgr *manager ) { plInputInterface::Init( manager ); // To get the pipeline fPipe = nil; plgDispatch::Dispatch()->RegisterForExactType( plRenderMsg::Index(), fManager->GetKey() ); plgDispatch::Dispatch()->RegisterForExactType( plInputIfaceMgrMsg::Index(), fManager->GetKey() ); plgDispatch::Dispatch()->RegisterForExactType( plPlayerPageMsg::Index(), fManager->GetKey() ); fCurrentClickable = nil; fCurrentClickableLogicMod = nil; fLastClicked = nil; fButtonState = 0; fClickability = 1; // Hack for clickable avatars, we need to always do the LOS check fLastClickIsAvatar = false; fCurrClickIsAvatar = false; fFadedLocalAvatar = false; fBookMode = kNotOffering; fOffereeKey = nil; fPendingLink = false; // register for control messages plCmdIfaceModMsg* pModMsg = new plCmdIfaceModMsg; pModMsg->SetBCastFlag(plMessage::kBCastByExactType); pModMsg->SetSender(fManager->GetKey()); pModMsg->SetCmd(plCmdIfaceModMsg::kAdd); plgDispatch::MsgSend(pModMsg); } void plSceneInputInterface::Shutdown( void ) { if( fPipe == nil ) plgDispatch::Dispatch()->UnRegisterForExactType( plRenderMsg::Index(), fManager->GetKey() ); else fPipe = nil; } void plSceneInputInterface::ClearClickableMap() { for (int i = 0; i < fClickableMap.Count(); i++) { clickableTest* pTest = fClickableMap[i]; delete(pTest); } fClickableMap.SetCountAndZero(0); } //// IHalfFadeAvatar ///////////////////////////////////////////////////////// void plSceneInputInterface::IHalfFadeAvatar(bool out) { plIfaceFadeAvatarMsg* pMsg = new plIfaceFadeAvatarMsg(); pMsg->SetSubjectKey(plNetClientMgr::GetInstance()->GetLocalPlayerKey()); pMsg->SetBCastFlag(plMessage::kBCastByExactType); pMsg->SetBCastFlag(plMessage::kNetPropagate, false); pMsg->SetFadeOut(out); pMsg->Send(); fFadedLocalAvatar = out; } void plSceneInputInterface::ResetClickableState() { if( fLastClicked != nil ) ISetLastClicked( nil, hsPoint3(0,0,0) ); ClearClickableMap(); fCurrentClickable = nil; fCurrentClickableLogicMod = nil; fCurrentCursor = SetCurrentCursorID(kNullCursor); fCurrClickIsAvatar = false; } //// IEval /////////////////////////////////////////////////////////////////// bool plSceneInputInterface::IEval( double secs, float del, uint32_t dirty ) { // this needs to always go no matter what... // ...unless we have cliclability disabled (as in the case of certain multistage behaviors) if (plMouseDevice::Instance()->GetCursorOpacity() > 0.f && !plMouseDevice::Instance()->GetHideCursor()) { IRequestLOSCheck( plMouseDevice::Instance()->GetCursorX(), plMouseDevice::Instance()->GetCursorY(), ID_FIND_LOCALPLAYER ); if (fClickability) IRequestLOSCheck( plMouseDevice::Instance()->GetCursorX(), plMouseDevice::Instance()->GetCursorY(), ID_FIND_CLICKABLE ); } else if (fFadedLocalAvatar) IHalfFadeAvatar(false); // if (!fCurrClickIsAvatar) // fCurrentCursor = SetCurrentCursorID(kNullCursor); // ping for possible cursor changes int i; for (i=0; i < fClickableMap.Count(); i++) { plFakeOutMsg *pMsg = new plFakeOutMsg; pMsg->SetSender( fManager->GetKey() ); pMsg->AddReceiver( fClickableMap[i]->key ); plgDispatch::MsgSend( pMsg ); } // then see if we have any bool change = false; for (i=0; i < fClickableMap.Count(); i++) { if( fClickableMap[i]->val ) { change = true; break; } } if (change) { if( fLastClicked != nil ) fCurrentCursor = SetCurrentCursorID(kCursorClicked); else fCurrentCursor = SetCurrentCursorID(kCursorPoised); } return true; } //// MsgReceive ////////////////////////////////////////////////////////////// bool plSceneInputInterface::MsgReceive( plMessage *msg ) { plLOSHitMsg *pLOSMsg = plLOSHitMsg::ConvertNoRef( msg ); if( pLOSMsg ) { if( pLOSMsg->fRequestID == ID_FIND_CLICKABLE ) { bool clearCursor = false; if (!fClickability) return true; if( pLOSMsg->fObj ) { // is this object clickable? plSceneObject *pObj = plSceneObject::ConvertNoRef( pLOSMsg->fObj->ObjectIsLoaded() ); if( pObj ) { if (fShowLOS) { if (pLOSMsg->fNoHit) DetectorLogSpecial("%s: LOS miss", pObj->GetKeyName().c_str()); else DetectorLogSpecial("%s: LOS hit", pObj->GetKeyName().c_str()); } int i; const plInterfaceInfoModifier* pMod = 0; for( i = 0; i < pObj->GetNumModifiers(); i++ ) { if (fBookMode == kNotOffering) // when sharing a book we don't care about other clickables { pMod = plInterfaceInfoModifier::ConvertNoRef( pObj->GetModifier(i) ); if (pMod) // we found our list, stop here { plLogicModBase* pLogicMod = (plLogicModBase*)pObj->GetModifierByType(plLogicModBase::Index()); if (!pLogicMod) return true; if (fCurrentClickable != pObj->GetKey()) { // is it the current clickable already? ClearClickableMap(); fCurrentCursor = SetCurrentCursorID(kNullCursor); fCurrentClickable = pObj->GetKey(); fCurrentClickableLogicMod = pLogicMod->GetKey(); fCurrentClickPoint = pLOSMsg->fHitPoint; for (int x = 0; x < pMod->GetNumReferencedKeys(); x++) fClickableMap.Append( new clickableTest(pMod->GetReferencedKey(x))); } else { // even if this is still the same clickable object, the cursor could be // ...at a different spot on the clickable, so save that fCurrentClickPoint = pLOSMsg->fHitPoint; } fCurrClickIsAvatar = false; return true; } } // see if it is an avatar plArmatureMod* armMod = (plArmatureMod*)plArmatureMod::ConvertNoRef( pObj->GetModifier(i)); if (armMod) { if (armMod->IsMidLink()) return true; // okay, are we a CCR? bool amCCR = plNetClientMgr::GetInstance()->GetCCRLevel(); // is this person a NPC or CCR? int mbrIdx=plNetClientMgr::GetInstance()->TransportMgr().FindMember(pObj->GetKey()); plNetTransportMember* pMbr = plNetClientMgr::GetInstance()->TransportMgr().GetMember(mbrIdx); if (!pMbr) // whoops - it's a freakin' NPC ! return true; if (pMbr->IsCCR()) { if (amCCR) { // we can click on them plMouseDevice::AddCCRToCursor(); } else { // nope return true; } } // now, if I am a CCR, let me click on anyone at any time if (amCCR) { ClearClickableMap(); fCurrentClickable = pObj->GetKey(); fCurrentClickableLogicMod = nil; fCurrClickIsAvatar = true; fCurrentCursor = SetCurrentCursorID(kCursorPoised); // not sure why we need to point on the avatar... // ...but maybe something in the future will need this fCurrentClickPoint = pLOSMsg->fHitPoint; plMouseDevice::AddNameToCursor(plNetClientMgr::GetInstance()->GetPlayerName(fCurrentClickable)); // also add their player ID to the cursor plMouseDevice::AddIDNumToCursor(pMbr->GetPlayerID()); return true; } // otherwise, cull people as necessary // also make sure that they are not in our ignore list else if (VaultAmIgnoringPlayer( pMbr->GetPlayerID())) return true; // further, if we are offering a book, only allow clicks on the person // whom we've already offered it to (to cancel it) else if (fBookMode == kBookOffered && pObj->GetKey() != fOffereeKey) return true; // within distance // also... make sure they aren't off climbing a ladder or looking at their KI else if (fBookMode == kOfferBook) { plArmatureBrain* curBrain = armMod->GetCurrentBrain(); if (curBrain) { if (curBrain->IsRunningTask()) { fCurrentCursor = SetCurrentCursorID(kCursorClickDisabled); plMouseDevice::AddNameToCursor(plNetClientMgr::GetInstance()->GetPlayerName(pObj->GetKey())); return true; } } plAvatarMgr* aMgr = plAvatarMgr::GetInstance(); if (aMgr) { if (aMgr->IsACoopRunning()) { fCurrentCursor = SetCurrentCursorID(kCursorClickDisabled); plMouseDevice::AddNameToCursor(plNetClientMgr::GetInstance()->GetPlayerName(pObj->GetKey())); return true; } } plSceneObject* locPlayer = (plSceneObject*)plNetClientMgr::GetInstance()->GetLocalPlayer(); // make sure that they are facing each other if ( locPlayer ) { hsVector3 ourView = locPlayer->GetCoordinateInterface()->GetLocalToWorld().GetAxis(hsMatrix44::kView); hsVector3 theirView = pObj->GetCoordinateInterface()->GetLocalToWorld().GetAxis(hsMatrix44::kView); float viewdot = ourView * theirView; hsVector3 towards(locPlayer->GetCoordinateInterface()->GetLocalToWorld().GetTranslate() - pObj->GetCoordinateInterface()->GetLocalToWorld().GetTranslate()); towards.Normalize(); float towardsdot = ourView * towards; if (viewdot > SHARE_FACING_TOLERANCE || towardsdot > SHARE_FACING_TOLERANCE ) { ResetClickableState(); return true; // not facing enough... reject } } //otherwise make sure that they are close enough to click on if (locPlayer) { hsPoint3 avPt = locPlayer->GetCoordinateInterface()->GetLocalToWorld().GetTranslate(); hsPoint3 objPt = pObj->GetCoordinateInterface()->GetLocalToWorld().GetTranslate(); hsVector3 dist(avPt - objPt); if ( dist.MagnitudeSquared() >= 16.0f ) // you are too far away { ResetClickableState(); return true; } if (hsABS(avPt.fZ - objPt.fZ) > 1.0f) // you need to also be in the same plane (some books are on top of rocks you need to jump onto) { ResetClickableState(); return true; } } } // finally - make sure this guy is not in our ignore lists. int x; for (x = 0; x < fIgnoredAvatars.Count(); x++) { if (fIgnoredAvatars[x] == pObj->GetKey()) { fCurrentCursor = SetCurrentCursorID(kCursorClickDisabled); plMouseDevice::AddNameToCursor(plNetClientMgr::GetInstance()->GetPlayerName(pObj->GetKey())); return true; } } for (x = 0; x < fGUIIgnoredAvatars.Count(); x++) { if (fGUIIgnoredAvatars[x] == pObj->GetKey()) { fCurrentCursor = SetCurrentCursorID(kCursorClickDisabled); plMouseDevice::AddNameToCursor(plNetClientMgr::GetInstance()->GetPlayerName(pObj->GetKey())); return true; } } ClearClickableMap(); fCurrentClickable = pObj->GetKey(); fCurrentClickableLogicMod = nil; fCurrClickIsAvatar = true; fCurrentCursor = SetCurrentCursorID(kCursorPoised); // not sure why we need to point on the avatar... // ...but maybe something in the future will need this fCurrentClickPoint = pLOSMsg->fHitPoint; plMouseDevice::AddNameToCursor(plNetClientMgr::GetInstance()->GetPlayerName(fCurrentClickable)); return true; } } // here! it's an object which is not clickable // no object, or not clickable or avatar fCurrentClickPoint = pLOSMsg->fHitPoint; ResetClickableState(); return false; } } // no object, or not clickable or avatar ResetClickableState(); } else if( pLOSMsg->fRequestID == ID_FIND_LOCALPLAYER ) { bool result = false; if( pLOSMsg->fObj ) { // is this object clickable? plSceneObject *pObj = plSceneObject::ConvertNoRef( pLOSMsg->fObj->ObjectIsLoaded() ); if( pObj ) { if (pObj == plNetClientMgr::GetInstance()->GetLocalPlayer()) result = true; } } if (result && !fFadedLocalAvatar) { IHalfFadeAvatar(true); return true; } else if (!result && fFadedLocalAvatar) { IHalfFadeAvatar(false); return true; } } if( pLOSMsg->fRequestID == ID_FIND_WALKABLE_GROUND ) { if (!pLOSMsg->fNoHit) { plAvatarMgr::GetInstance()->GetLocalAvatar()->TurnToPoint(pLOSMsg->fHitPoint); } return true; } return true; } plCursorChangeMsg *fakeReplyMsg = plCursorChangeMsg::ConvertNoRef( msg ); if( fakeReplyMsg != nil ) { bool deniedCurrent = false; plKey key = fakeReplyMsg->GetSender(); for (int i = 0; i < fClickableMap.Count(); i++) { if (fClickableMap[i]->key == key) { if( fakeReplyMsg->fType == plCursorChangeMsg::kNullCursor ) { // Means not clickable--gotta fix this someday fClickableMap[i]->val = false; if (fClickableMap[i]->key == fCurrentClickableLogicMod) { deniedCurrent = true; break; } } else { // And fix this... fClickableMap[i]->val = true; } } } if (deniedCurrent) ResetClickableState(); return true; } plRenderMsg *rMsg = plRenderMsg::ConvertNoRef( msg ); if( rMsg != nil ) { fPipe = rMsg->Pipeline(); plgDispatch::Dispatch()->UnRegisterForExactType( plRenderMsg::Index(), fManager->GetKey() ); return true; } // reply from coop share book multistage plNotifyMsg* pNMsg = plNotifyMsg::ConvertNoRef(msg); if (pNMsg) { for(int x=0; x < pNMsg->GetEventCount();x++) { proEventData* pED = pNMsg->GetEventRecord(0); if ( pED->fEventType == proEventData::kMultiStage ) { proMultiStageEventData* pMS = (proMultiStageEventData*)pED; if (pMS->fAvatar == fOffereeKey) // mojo has linked { // do something - they linked out but we are still in the multistage fOffereeKey = nil; } else if (pMS->fAvatar == plNetClientMgr::GetInstance()->GetLocalPlayerKey()) { // do something else if (fBookMode = kNotOffering && fPendingLink == false) // we just linked out { // make me clickable again ISendAvatarDisabledNotification(true); } else // we put the book back after our target linked out { fBookMode = kNotOffering; fPendingLink = false; // make ME clickable again ISendAvatarDisabledNotification(true); } } return true; } } return false; } // if someone pages out / in, remove them from our ignore list or notify them to ignore us plPlayerPageMsg* pPlayerMsg = plPlayerPageMsg::ConvertNoRef(msg); if (pPlayerMsg) { if (pPlayerMsg->fUnload) { int x; // first, remove this avatar from my list of avatars I ingore for clickable griefing (when the 'ignore avatars' key is pressed) for(x = 0; x < fLocalIgnoredAvatars.Count(); x++) { if (fLocalIgnoredAvatars[x] == pPlayerMsg->fPlayer) fLocalIgnoredAvatars.RemoveItem(pPlayerMsg->fPlayer); } // now deal with avatars we are always ignoring because of their current activity for(x = 0; x < fIgnoredAvatars.Count(); x++) { if (fIgnoredAvatars[x] == pPlayerMsg->fPlayer) fIgnoredAvatars.RemoveItem(pPlayerMsg->fPlayer); } for(x = 0; x < fGUIIgnoredAvatars.Count(); x++) { if (fGUIIgnoredAvatars[x] == pPlayerMsg->fPlayer) fGUIIgnoredAvatars.RemoveItem(pPlayerMsg->fPlayer); } if (fOffereeKey == pPlayerMsg->fPlayer) { if (fBookMode == kBookOffered) { // and put our own dialog back up... plKey avKey = plNetClientMgr::GetInstance()->GetLocalPlayerKey(); ISendOfferNotification(avKey, 0, false); //IManageIgnoredAvatars(fOffereeKey, false); fOffereeKey = nil; fBookMode = kNotOffering; ISendAvatarDisabledNotification(true); } } } else { // add them to the list we keep of everyone here: // but DO NOT add the local avatar if (pPlayerMsg->fPlayer != plNetClientMgr::GetInstance()->GetLocalPlayerKey()) fLocalIgnoredAvatars.Append(pPlayerMsg->fPlayer); if (fBookMode != kNotOffering) { // tell them to ignore us plInputIfaceMgrMsg* pMsg = new plInputIfaceMgrMsg(plInputIfaceMgrMsg::kDisableAvatarClickable); plKey avKey = plNetClientMgr::GetInstance()->GetLocalPlayerKey(); pMsg->SetAvKey(avKey); pMsg->SetBCastFlag(plMessage::kNetPropagate); pMsg->SetBCastFlag(plMessage::kNetForce); pMsg->SetBCastFlag(plMessage::kLocalPropagate, false); pMsg->AddNetReceiver( pPlayerMsg->fClientID ); pMsg->Send(); // and tell them to ignore our victim //plInputIfaceMgrMsg* pMsg2 = new plInputIfaceMgrMsg(plInputIfaceMgrMsg::kDisableAvatarClickable); //pMsg2->SetAvKey(fOffereeKey); //pMsg2->SetBCastFlag(plMessage::kNetPropagate); //pMsg2->SetBCastFlag(plMessage::kNetForce); //pMsg2->SetBCastFlag(plMessage::kLocalPropagate, false); //pMsg2->AddNetReceiver( pPlayerMsg->fClientID ); //pMsg2->Send(); } // tell them to ingore us if we are looking at a GUI for(int x = 0; x < fGUIIgnoredAvatars.Count(); x++) { if (fGUIIgnoredAvatars[x] == plNetClientMgr::GetInstance()->GetLocalPlayerKey()) { plInputIfaceMgrMsg* pMsg3 = new plInputIfaceMgrMsg(plInputIfaceMgrMsg::kGUIDisableAvatarClickable); pMsg3->SetAvKey(fGUIIgnoredAvatars[x]); pMsg3->SetBCastFlag(plMessage::kNetPropagate); pMsg3->SetBCastFlag(plMessage::kNetForce); pMsg3->SetBCastFlag(plMessage::kLocalPropagate, false); pMsg3->AddNetReceiver( pPlayerMsg->fClientID ); pMsg3->Send(); return true; } } } } plInputIfaceMgrMsg *mgrMsg = plInputIfaceMgrMsg::ConvertNoRef( msg ); if( mgrMsg != nil ) { if ( mgrMsg->GetCommand() == plInputIfaceMgrMsg::kDisableAvatarClickable ) { // ignore if already in list or this is who WE are offering the book to... if (mgrMsg->GetAvKey() == fOffereeKey) return true; for(int x = 0; x < fIgnoredAvatars.Count(); x++) { if (fIgnoredAvatars[x] == mgrMsg->GetAvKey()) return true; } fIgnoredAvatars.Append(mgrMsg->GetAvKey()); } else if ( mgrMsg->GetCommand() == plInputIfaceMgrMsg::kEnableAvatarClickable ) { for(int x = 0; x < fIgnoredAvatars.Count(); x++) { if (fIgnoredAvatars[x] == mgrMsg->GetAvKey()) fIgnoredAvatars.RemoveItem(mgrMsg->GetAvKey()); } } else if ( mgrMsg->GetCommand() == plInputIfaceMgrMsg::kGUIDisableAvatarClickable ) { // ignore if already in list or this is who WE are offering the book to... if (mgrMsg->GetAvKey() == fOffereeKey) return true; for(int x = 0; x < fGUIIgnoredAvatars.Count(); x++) { if (fGUIIgnoredAvatars[x] == mgrMsg->GetAvKey()) return true; } fGUIIgnoredAvatars.Append(mgrMsg->GetAvKey()); } else if ( mgrMsg->GetCommand() == plInputIfaceMgrMsg::kGUIEnableAvatarClickable ) { for(int x = 0; x < fGUIIgnoredAvatars.Count(); x++) { if (fGUIIgnoredAvatars[x] == mgrMsg->GetAvKey()) fGUIIgnoredAvatars.RemoveItem(mgrMsg->GetAvKey()); } } else if( mgrMsg->GetCommand() == plInputIfaceMgrMsg::kEnableClickables ) { fClickability = true; return true; } else if( mgrMsg->GetCommand() == plInputIfaceMgrMsg::kDisableClickables ) { fClickability = false; ResetClickableState(); return true; } else if ( mgrMsg->GetCommand() == plInputIfaceMgrMsg::kSetOfferBookMode ) { fBookMode = kOfferBook; fOffereeKey = nil; fBookKey = mgrMsg->GetSender(); fOfferedAgeInstance = mgrMsg->GetAgeName(); fOfferedAgeFile = mgrMsg->GetAgeFileName(); ISendAvatarDisabledNotification(false); } else if ( mgrMsg->GetCommand() == plInputIfaceMgrMsg::kClearOfferBookMode ) { if (fBookMode == kOfferAccepted || fBookMode == kOfferLinkPending) { fPendingLink = true; } else if (fOffereeKey != nil) { // notify any offeree that the offer is rescinded ISendOfferNotification(fOffereeKey, -999, true); //IManageIgnoredAvatars(fOffereeKey, false); fOffereeKey = nil; } // shut down offer book mode fBookMode = kNotOffering; ISendAvatarDisabledNotification(true); } else if ( mgrMsg->GetCommand() == plInputIfaceMgrMsg::kNotifyOfferRejected) { if (fBookMode == kBookOffered) { // and put our own dialog back up... plKey avKey = plNetClientMgr::GetInstance()->GetLocalPlayerKey(); ISendOfferNotification(avKey, 0, false); //IManageIgnoredAvatars(fOffereeKey, false); fBookMode = kOfferBook; fOffereeKey = nil; } else if (mgrMsg->GetSender() == plNetClientMgr::GetInstance()->GetLocalPlayerKey()) { fBookMode = kNotOffering; ISendAvatarDisabledNotification(true); } } else if ( mgrMsg->GetCommand() == plInputIfaceMgrMsg::kNotifyOfferAccepted && fBookMode == kBookOffered) { fBookMode = kOfferAccepted; } else if ( mgrMsg->GetCommand() == plInputIfaceMgrMsg::kNotifyOfferCompleted ) { // must have actually offered the book... if (!fPendingLink) { if (fBookMode == kOfferBook || fBookMode == kBookOffered) return true; } if (!plNetClientMgr::GetInstance()) return true; fOffereeID = mgrMsg->GetPageID(); ILinkOffereeToAge(); } else if ( mgrMsg->GetCommand() == plInputIfaceMgrMsg::kSetShareSpawnPoint ) { fSpawnPoint = mgrMsg->GetSpawnPoint(); } else if ( mgrMsg->GetCommand() == plInputIfaceMgrMsg::kSetShareAgeInstanceGuid ) { fAgeInstanceGuid = plUUID(mgrMsg->GetAgeInstanceGuid()); } } plVaultNotifyMsg* pVaultMsg = plVaultNotifyMsg::ConvertNoRef(msg); if (pVaultMsg && pVaultMsg->GetType()==plNetCommon::VaultTasks::kRegisterOwnedAge ) { //sanity check - if (fBookMode != kOfferLinkPending && fPendingLink == false) return true; // stop looking for this message and reset interface to 'offer book' mode again plgDispatch::Dispatch()->UnRegisterForExactType(plVaultNotifyMsg::Index(), fManager->GetKey()); ILinkOffereeToAge(); return true; } return false; } ///// ILinkOffereeToAge void plSceneInputInterface::ILinkOffereeToAge() { // check vault to see if we've got an instance of the offered age now, if not create one and wait until we get a reply... plAgeInfoStruct info; info.SetAgeFilename(fOfferedAgeFile); info.SetAgeInstanceName(fOfferedAgeInstance); bool isAgeInstanceGuidSet = fAgeInstanceGuid.IsSet(); plAgeLinkStruct link; if (isAgeInstanceGuidSet) { info.SetAgeInstanceGuid(&fAgeInstanceGuid); link.GetAgeInfo()->CopyFrom(&info); fAgeInstanceGuid.Clear(); } else if (!VaultGetOwnedAgeLink(&info, &link)) { // We must have an owned copy of the age before we can offer it, so make one now plUUID guid = plUUID::Generate(); info.SetAgeInstanceGuid(&guid); plString title, desc; unsigned nameLen = plNetClientMgr::GetInstance()->GetPlayerName().GetSize(); if (plNetClientMgr::GetInstance()->GetPlayerName().CharAt(nameLen - 1) == 's' || plNetClientMgr::GetInstance()->GetPlayerName().CharAt(nameLen - 1) == 'S') { title = plString::Format( "%s'", plNetClientMgr::GetInstance()->GetPlayerName().c_str() ); desc = plString::Format( "%s' %s", plNetClientMgr::GetInstance()->GetPlayerName().c_str(), link.GetAgeInfo()->GetAgeInstanceName() ); } else { title = plString::Format( "%s's", plNetClientMgr::GetInstance()->GetPlayerName().c_str() ); desc = plString::Format( "%s's %s", plNetClientMgr::GetInstance()->GetPlayerName().c_str(), link.GetAgeInfo()->GetAgeInstanceName() ); } info.SetAgeUserDefinedName( title.c_str() ); info.SetAgeDescription( desc.c_str() ); link.GetAgeInfo()->CopyFrom(&info); if (!VaultRegisterOwnedAgeAndWait(&link)) { // failed to become an owner of the age for some reason, offer cannot continue return; } } else if (RelVaultNode * linkNode = VaultGetOwnedAgeLinkIncRef(&info)) { // We have the age in our AgesIOwnFolder. If its volatile, dump it for the new one. VaultAgeLinkNode linkAcc(linkNode); if (linkAcc.GetVolatile()) { if (VaultUnregisterOwnedAgeAndWait(link.GetAgeInfo())) { plUUID guid = plUUID::Generate(); link.GetAgeInfo()->SetAgeInstanceGuid(&guid); VaultRegisterOwnedAgeAndWait(&link); } } linkNode->DecRef(); } if (fSpawnPoint) { plSpawnPointInfo spawnPoint; spawnPoint.SetName(fSpawnPoint); link.SetSpawnPoint(spawnPoint); } // We now own the age, offer it if (0 == stricmp(fOfferedAgeFile, kPersonalAgeFilename)) plNetLinkingMgr::GetInstance()->OfferLinkToPlayer(&link, fOffereeID, fManager->GetKey()); else plNetLinkingMgr::GetInstance()->LinkPlayerToAge(&link, fOffereeID); if (!fPendingLink && stricmp(fOfferedAgeFile, kPersonalAgeFilename)) { // tell our local dialog to pop up again... plKey avKey = plNetClientMgr::GetInstance()->GetLocalPlayerKey(); ISendOfferNotification(avKey, 0, false); // make them clickable again(in case they come back?) //IManageIgnoredAvatars(fOffereeKey, false); fBookMode = kNotOffering; fOffereeKey = nil; fPendingLink = false; } else // this is a yeesha book link, must wait for multistage callbacks { // commented out until after 0.9 fBookMode = kOfferLinkPending; fPendingLink = true; // fBookMode = kNotOffering; // fOffereeKey = nil; // fPendingLink = false; // ISendAvatarDisabledNotification(true); } } //// ISetLastClicked ///////////////////////////////////////////////////////// #define MATT_WAS_HERE void plSceneInputInterface::ISetLastClicked( plKey obj, hsPoint3 hitPoint ) { if (fBookMode != kNotOffering) return; if( fLastClicked != nil ) { // Send an "un-picked" message to it if( !fLastClickIsAvatar ) { plPickedMsg *pPickedMsg = new plPickedMsg; pPickedMsg->AddReceiver( fLastClicked ); pPickedMsg->fPicked = false; plgDispatch::MsgSend( pPickedMsg ); } else { plRemoteAvatarInfoMsg *pMsg = new plRemoteAvatarInfoMsg; pMsg->SetAvatarKey( nil ); plgDispatch::MsgSend( pMsg ); } } fLastClicked = obj; fLastClickIsAvatar = ( obj == nil ) ? false : fCurrClickIsAvatar; if( fLastClicked != nil ) { #ifdef MATT_WAS_HERE // now we send pick messages to avatars as well... plPickedMsg *pPickedMsg = new plPickedMsg; pPickedMsg->AddReceiver( fLastClicked ); pPickedMsg->fHitPoint = hitPoint; plgDispatch::MsgSend( pPickedMsg ); // if it's an avatar, we also send this thing if(fLastClickIsAvatar) { plRemoteAvatarInfoMsg *pMsg = new plRemoteAvatarInfoMsg; pMsg->SetAvatarKey( fLastClicked ); plgDispatch::MsgSend( pMsg ); } #else // Send a "picked" message to it if( !fLastClickIsAvatar ) { plPickedMsg *pPickedMsg = new plPickedMsg; pPickedMsg->AddReceiver( fLastClicked ); pPickedMsg->fHitPoint = hitPoint; plgDispatch::MsgSend( pPickedMsg ); } else { plRemoteAvatarInfoMsg *pMsg = new plRemoteAvatarInfoMsg; pMsg->SetAvatarKey( fLastClicked ); plgDispatch::MsgSend( pMsg ); } #endif } } //// InterpretInputEvent ///////////////////////////////////////////////////// bool plSceneInputInterface::InterpretInputEvent( plInputEventMsg *pMsg ) { plControlEventMsg* pControlEvent = plControlEventMsg::ConvertNoRef(pMsg); if (pControlEvent) { if (pControlEvent->GetControlCode() == B_CONTROL_IGNORE_AVATARS) { for (int i = 0; i < fLocalIgnoredAvatars.Count(); i++) { plSceneObject* pObj = plSceneObject::ConvertNoRef(fLocalIgnoredAvatars[i]->ObjectIsLoaded()); if (!pObj) continue; const plArmatureMod* pArm = (const plArmatureMod*)pObj->GetModifierByType(plArmatureMod::Index()); if (!pArm) continue; plPhysicalControllerCore* controller = pArm->GetController(); if (controller) { if (pControlEvent->ControlActivated()) controller->SetLOSDB(plSimDefs::kLOSDBNone); else controller->SetLOSDB(plSimDefs::kLOSDBUIItems); } } return true; } return false; } plMouseEventMsg *mouseMsg = plMouseEventMsg::ConvertNoRef( pMsg ); if( mouseMsg != nil ) { // you're suspended when in this mode... if (fBookMode == kOfferLinkPending || fBookMode == kOfferAccepted) return true; if( mouseMsg->GetButton() == kLeftButtonDown ) { if( fCurrentClickable != nil && fLastClicked == nil && fCurrentCursor != kNullCursor ) { fButtonState |= kLeftButtonDown; ISetLastClicked( fCurrentClickable, fCurrentClickPoint ); fCurrentCursor = SetCurrentCursorID(kCursorClicked); return true; } // right here if (fBookMode == kOfferBook) { fBookMode = kNotOffering; fOffereeKey = nil; ISendAvatarDisabledNotification(true); } } else if( mouseMsg->GetButton() == kLeftButtonUp ) { if (fBookMode != kNotOffering) { if (fBookMode == kOfferBook && fCurrClickIsAvatar) { // send the avatar a message to put up his appropriate book ISendOfferNotification(fCurrentClickable, 999, true); //IManageIgnoredAvatars(fCurrentClickable, true); fBookMode = kBookOffered; fOffereeKey = fCurrentClickable; } else if (fBookMode == kBookOffered && fCurrClickIsAvatar) { // and put our own dialog back up... ISendOfferNotification(fOffereeKey, -999, true); plKey avKey = plNetClientMgr::GetInstance()->GetLocalPlayerKey(); ISendOfferNotification(avKey, 0, false); //IManageIgnoredAvatars(fOffereeKey, false); fBookMode = kOfferBook; fOffereeKey = nil; } else if (fBookMode == kOfferBook) { fBookMode = kNotOffering; fOffereeKey = nil; ISendAvatarDisabledNotification(true); } } if( fLastClicked != nil ) { fButtonState &= ~kLeftButtonDown; ISetLastClicked( nil, hsPoint3(0,0,0) ); return true; } } } return false; } //// ISendOfferNotification //////////////////////////////////////////////////////// void plSceneInputInterface::IManageIgnoredAvatars(plKey& offeree, bool add) { // tell everyone else to be able to / not to be able to select this avatar plInputIfaceMgrMsg* pMsg = 0; if (!add) pMsg = new plInputIfaceMgrMsg(plInputIfaceMgrMsg::kEnableAvatarClickable); else pMsg = new plInputIfaceMgrMsg(plInputIfaceMgrMsg::kDisableAvatarClickable); pMsg->SetAvKey(offeree); pMsg->SetBCastFlag(plMessage::kNetPropagate); pMsg->SetBCastFlag(plMessage::kNetForce); pMsg->SetBCastFlag(plMessage::kLocalPropagate, false); pMsg->Send(); } void plSceneInputInterface::ISendOfferNotification(plKey& offeree, int ID, bool net) { int offereeID = -1; if (offeree == plNetClientMgr::GetInstance()->GetLocalPlayerKey()) { offereeID = plNetClientMgr::GetInstance()->GetPlayerID(); } else { plNetTransportMember **members = nil; plNetClientMgr::GetInstance()->TransportMgr().GetMemberListDistSorted( members ); if( members != nil) { for(int i = 0; i < plNetClientMgr::GetInstance()->TransportMgr().GetNumMembers(); i++ ) { plNetTransportMember *mbr = members[ i ]; if( mbr != nil && mbr->GetAvatarKey() == offeree) { offereeID = mbr->GetPlayerID(); break; } } } delete [] members; } plNotifyMsg* pMsg = new plNotifyMsg; pMsg->AddOfferBookEvent(plNetClientMgr::GetInstance()->GetLocalPlayerKey(), ID, offereeID); pMsg->AddReceiver(fBookKey); if (net) { pMsg->SetBCastFlag(plMessage::kNetPropagate); pMsg->SetBCastFlag(plMessage::kNetForce); pMsg->SetBCastFlag(plMessage::kLocalPropagate,false); pMsg->AddNetReceiver( offereeID ); pMsg->Send(); } else { pMsg->SetBCastFlag(plMessage::kNetPropagate, false); // don't deliver networked! pMsg->Send(); } } void plSceneInputInterface::ISendAvatarDisabledNotification(bool enabled) { plInputIfaceMgrMsg* pMsg = 0; if (enabled) pMsg = new plInputIfaceMgrMsg(plInputIfaceMgrMsg::kEnableAvatarClickable); else pMsg = new plInputIfaceMgrMsg(plInputIfaceMgrMsg::kDisableAvatarClickable); plKey avKey = plNetClientMgr::GetInstance()->GetLocalPlayerKey(); pMsg->SetAvKey(avKey); pMsg->SetBCastFlag(plMessage::kNetPropagate); pMsg->SetBCastFlag(plMessage::kNetForce); pMsg->SetBCastFlag(plMessage::kLocalPropagate, false); pMsg->Send(); } //// IRequestLOSCheck //////////////////////////////////////////////////////// void plSceneInputInterface::IRequestLOSCheck( float xPos, float yPos, int ID ) { if( fPipe == nil ) return; int32_t x=(int32_t) ( xPos * fPipe->Width() ); int32_t y=(int32_t) ( yPos * fPipe->Height() ); hsPoint3 endPos, startPos; fPipe->ScreenToWorldPoint( 1,0, &x, &y, 10000, 0, &endPos ); startPos = fPipe->GetViewPositionWorld(); // move the start pos out a little to avoid backing up against physical objects... hsVector3 view(endPos - startPos); view.Normalize(); startPos = startPos + (view * 0.3f); plLOSRequestMsg* pMsg; if(ID == ID_FIND_CLICKABLE) { pMsg = new plLOSRequestMsg( fManager->GetKey(), startPos, endPos, plSimDefs::kLOSDBUIItems, plLOSRequestMsg::kTestClosest ); pMsg->SetCullDB(plSimDefs::kLOSDBUIBlockers); } else if(ID == ID_FIND_WALKABLE_GROUND) { pMsg = new plLOSRequestMsg( fManager->GetKey(), startPos, endPos, plSimDefs::kLOSDBAvatarWalkable, plLOSRequestMsg::kTestClosest); } else pMsg = new plLOSRequestMsg( fManager->GetKey(), startPos, endPos, plSimDefs::kLOSDBLocalAvatar, plLOSRequestMsg::kTestClosest); pMsg->SetReportType( plLOSRequestMsg::kReportHitOrMiss ); pMsg->SetRequestID( ID ); plgDispatch::MsgSend( pMsg ); fLastStartPt = startPos; fLastEndPt = endPos; } //// IWorldPosMovedSinceLastLOSCheck ///////////////////////////////////////// bool plSceneInputInterface::IWorldPosMovedSinceLastLOSCheck( void ) { if( fPipe == nil ) return false; int32_t x=(int32_t) ( plMouseDevice::Instance()->GetCursorX() * fPipe->Width() ); int32_t y=(int32_t) ( plMouseDevice::Instance()->GetCursorY() * fPipe->Height() ); hsPoint3 endPos, startPos; startPos = fPipe->GetViewPositionWorld(); if( !( startPos == fLastStartPt ) ) return true; fPipe->ScreenToWorldPoint( 1,0, &x, &y, 10000, 0, &endPos ); if( !( endPos == fLastEndPt ) ) return true; return false; } //// GetCurrentCursorID /////////////////////////////////////////////////////// uint32_t plSceneInputInterface::SetCurrentCursorID(uint32_t id) { if (fBookMode == kOfferBook || fBookMode == kBookOffered) { switch(id) { case kCursorPoised: return kCursorOfferBookHilite; case kNullCursor: return kCursorOfferBook; case kCursorClicked: return kCursorOfferBookClicked; } } else if (fBookMode == kOfferAccepted || fBookMode == kOfferLinkPending) return kCursorOfferBook; return id; } void plSceneInputInterface::RequestAvatarTurnToPointLOS() { IRequestLOSCheck( plMouseDevice::Instance()->GetCursorX(), plMouseDevice::Instance()->GetCursorY(), ID_FIND_WALKABLE_GROUND ); }