/*==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==*/ ////////////////////////////////////////////////////////////////////////////// // // // plSceneInputInterface // // // ////////////////////////////////////////////////////////////////////////////// #include "hsConfig.h" #include "hsWindows.h" #include "hsTypes.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/plAvCallbackAction.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; hsBool plSceneInputInterface::fShowLOS = false; //// Constructor/Destructor ////////////////////////////////////////////////// plSceneInputInterface::plSceneInputInterface() { fPipe = nil; fSpawnPoint = nil; GuidClear(&fAgeInstanceGuid); 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 = TRACKED_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(hsBool out) { plIfaceFadeAvatarMsg* pMsg = TRACKED_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 /////////////////////////////////////////////////////////////////// hsBool plSceneInputInterface::IEval( double secs, hsScalar del, UInt32 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 = TRACKED_NEW plFakeOutMsg; pMsg->SetSender( fManager->GetKey() ); pMsg->AddReceiver( fClickableMap[i]->key ); plgDispatch::MsgSend( pMsg ); } // then see if we have any hsBool 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 ////////////////////////////////////////////////////////////// hsBool plSceneInputInterface::MsgReceive( plMessage *msg ) { plLOSHitMsg *pLOSMsg = plLOSHitMsg::ConvertNoRef( msg ); if( pLOSMsg ) { if( pLOSMsg->fRequestID == ID_FIND_CLICKABLE ) { hsBool 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()); else DetectorLogSpecial("%s: LOS hit", pObj->GetKeyName()); } 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( TRACKED_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? hsBool 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); hsScalar viewdot = ourView * theirView; hsVector3 towards(locPlayer->GetCoordinateInterface()->GetLocalToWorld().GetTranslate() - pObj->GetCoordinateInterface()->GetLocalToWorld().GetTranslate()); towards.Normalize(); hsScalar 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 ) { hsBool 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... ISendOfferNotification(plNetClientMgr::GetInstance()->GetLocalPlayerKey(), 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 = TRACKED_NEW plInputIfaceMgrMsg(plInputIfaceMgrMsg::kDisableAvatarClickable); pMsg->SetAvKey(plNetClientMgr::GetInstance()->GetLocalPlayerKey()); 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 = TRACKED_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 = TRACKED_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... ISendOfferNotification(plNetClientMgr::GetInstance()->GetLocalPlayerKey(), 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 = 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 = !GuidIsNil(fAgeInstanceGuid); plAgeLinkStruct link; if (isAgeInstanceGuidSet) { info.SetAgeInstanceGuid(&plUUID(fAgeInstanceGuid)); link.GetAgeInfo()->CopyFrom(&info); GuidClear(&fAgeInstanceGuid); } else if (!VaultGetOwnedAgeLink(&info, &link)) { // We must have an owned copy of the age before we can offer it, so make one now info.SetAgeInstanceGuid(&plUUID(GuidGenerate())); std::string title; std::string desc; unsigned nameLen = StrLen(plNetClientMgr::GetInstance()->GetPlayerName()); if (plNetClientMgr::GetInstance()->GetPlayerName()[nameLen - 1] == 's' || plNetClientMgr::GetInstance()->GetPlayerName()[nameLen - 1] == 'S') { xtl::format( title, "%s'", plNetClientMgr::GetInstance()->GetPlayerName() ); xtl::format( desc, "%s' %s", plNetClientMgr::GetInstance()->GetPlayerName(), link.GetAgeInfo()->GetAgeInstanceName() ); } else { xtl::format( title, "%s's", plNetClientMgr::GetInstance()->GetPlayerName() ); xtl::format( desc, "%s's %s", plNetClientMgr::GetInstance()->GetPlayerName(), 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.volat) { if (VaultUnregisterOwnedAgeAndWait(link.GetAgeInfo())) { link.GetAgeInfo()->SetAgeInstanceGuid(&plUUID(GuidGenerate())); 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... ISendOfferNotification(plNetClientMgr::GetInstance()->GetLocalPlayerKey(), 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 = TRACKED_NEW plPickedMsg; pPickedMsg->AddReceiver( fLastClicked ); pPickedMsg->fPicked = false; plgDispatch::MsgSend( pPickedMsg ); } else { plRemoteAvatarInfoMsg *pMsg = TRACKED_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 = TRACKED_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 = TRACKED_NEW plRemoteAvatarInfoMsg; pMsg->SetAvatarKey( fLastClicked ); plgDispatch::MsgSend( pMsg ); } #else // Send a "picked" message to it if( !fLastClickIsAvatar ) { plPickedMsg *pPickedMsg = TRACKED_NEW plPickedMsg; pPickedMsg->AddReceiver( fLastClicked ); pPickedMsg->fHitPoint = hitPoint; plgDispatch::MsgSend( pPickedMsg ); } else { plRemoteAvatarInfoMsg *pMsg = TRACKED_NEW plRemoteAvatarInfoMsg; pMsg->SetAvatarKey( fLastClicked ); plgDispatch::MsgSend( pMsg ); } #endif } } //// InterpretInputEvent ///////////////////////////////////////////////////// hsBool 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); ISendOfferNotification(plNetClientMgr::GetInstance()->GetLocalPlayerKey(), 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, hsBool add) { // tell everyone else to be able to / not to be able to select this avatar plInputIfaceMgrMsg* pMsg = 0; if (!add) pMsg = TRACKED_NEW plInputIfaceMgrMsg(plInputIfaceMgrMsg::kEnableAvatarClickable); else pMsg = TRACKED_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, hsBool 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 = TRACKED_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(hsBool enabled) { plInputIfaceMgrMsg* pMsg = 0; if (enabled) pMsg = TRACKED_NEW plInputIfaceMgrMsg(plInputIfaceMgrMsg::kEnableAvatarClickable); else pMsg = TRACKED_NEW plInputIfaceMgrMsg(plInputIfaceMgrMsg::kDisableAvatarClickable); pMsg->SetAvKey(plNetClientMgr::GetInstance()->GetLocalPlayerKey()); pMsg->SetBCastFlag(plMessage::kNetPropagate); pMsg->SetBCastFlag(plMessage::kNetForce); pMsg->SetBCastFlag(plMessage::kLocalPropagate, false); pMsg->Send(); } //// IRequestLOSCheck //////////////////////////////////////////////////////// void plSceneInputInterface::IRequestLOSCheck( hsScalar xPos, hsScalar yPos, int ID ) { if( fPipe == nil ) return; Int32 x=(Int32) ( xPos * fPipe->Width() ); Int32 y=(Int32) ( 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 = TRACKED_NEW plLOSRequestMsg( fManager->GetKey(), startPos, endPos, plSimDefs::kLOSDBUIItems, plLOSRequestMsg::kTestClosest ); pMsg->SetCullDB(plSimDefs::kLOSDBUIBlockers); } else if(ID == ID_FIND_WALKABLE_GROUND) { pMsg = TRACKED_NEW plLOSRequestMsg( fManager->GetKey(), startPos, endPos, plSimDefs::kLOSDBAvatarWalkable, plLOSRequestMsg::kTestClosest); } else pMsg = TRACKED_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 ///////////////////////////////////////// hsBool plSceneInputInterface::IWorldPosMovedSinceLastLOSCheck( void ) { if( fPipe == nil ) return false; Int32 x=(Int32) ( plMouseDevice::Instance()->GetCursorX() * fPipe->Width() ); Int32 y=(Int32) ( 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 plSceneInputInterface::SetCurrentCursorID(UInt32 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 ); }