diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 06ce6c3d6c..dd1240058a 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -1306,6 +1306,9 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo _entityServerConnectionTimer.setSingleShot(true); connect(&_entityServerConnectionTimer, &QTimer::timeout, this, &Application::setFailedToConnectToEntityServer); + connect(&domainHandler, &DomainHandler::confirmConnectWithoutAvatarEntities, + this, &Application::confirmConnectWithoutAvatarEntities); + connect(&domainHandler, &DomainHandler::connectedToDomain, this, [this]() { if (!isServerlessMode()) { _entityServerConnectionTimer.setInterval(ENTITY_SERVER_ADDED_TIMEOUT); @@ -9172,6 +9175,32 @@ void Application::setShowBulletConstraintLimits(bool value) { _physicsEngine->setShowBulletConstraintLimits(value); } +void Application::confirmConnectWithoutAvatarEntities() { + + if (_confirmConnectWithoutAvatarEntitiesDialog) { + // Dialog is already displayed. + return; + } + + if (!getMyAvatar()->hasAvatarEntities()) { + // No avatar entities so continue with login. + DependencyManager::get()->getDomainHandler().setCanConnectWithoutAvatarEntities(true); + return; + } + + QString continueMessage = "Your wearables will not display on this domain. Continue?"; + _confirmConnectWithoutAvatarEntitiesDialog = OffscreenUi::asyncQuestion("Continue Without Wearables", continueMessage, + QMessageBox::Yes | QMessageBox::No); + if (_confirmConnectWithoutAvatarEntitiesDialog->getDialogItem()) { + QObject::connect(_confirmConnectWithoutAvatarEntitiesDialog, &ModalDialogListener::response, this, [=](QVariant answer) { + QObject::disconnect(_confirmConnectWithoutAvatarEntitiesDialog, &ModalDialogListener::response, this, nullptr); + _confirmConnectWithoutAvatarEntitiesDialog = nullptr; + bool shouldConnect = (static_cast(answer.toInt()) == QMessageBox::Yes); + DependencyManager::get()->getDomainHandler().setCanConnectWithoutAvatarEntities(shouldConnect); + }); + } +} + void Application::createLoginDialog() { const glm::vec3 LOGIN_DIMENSIONS { 0.89f, 0.5f, 0.01f }; const auto OFFSET = glm::vec2(0.7f, -0.1f); diff --git a/interface/src/Application.h b/interface/src/Application.h index 5cb5fdd5c0..55e412ee9f 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -50,6 +50,7 @@ #include #include #include +#include #include "avatar/MyAvatar.h" #include "FancyCamera.h" @@ -325,6 +326,8 @@ public: int getOtherAvatarsReplicaCount() { return DependencyManager::get()->getReplicaCount(); } void setOtherAvatarsReplicaCount(int count) { DependencyManager::get()->setReplicaCount(count); } + void confirmConnectWithoutAvatarEntities(); + bool getLoginDialogPoppedUp() const { return _loginDialogPoppedUp; } void createLoginDialog(); void updateLoginDialogPosition(); @@ -723,6 +726,8 @@ private: bool _loginDialogPoppedUp{ false }; bool _desktopRootItemCreated{ false }; + ModalDialogListener* _confirmConnectWithoutAvatarEntitiesDialog { nullptr }; + bool _developerMenuVisible{ false }; QString _previousAvatarSkeletonModel; float _previousAvatarTargetScale; diff --git a/interface/src/ConnectionMonitor.cpp b/interface/src/ConnectionMonitor.cpp index 070015f05b..cd5235e42b 100644 --- a/interface/src/ConnectionMonitor.cpp +++ b/interface/src/ConnectionMonitor.cpp @@ -35,6 +35,7 @@ void ConnectionMonitor::init() { connect(&domainHandler, &DomainHandler::connectedToDomain, this, &ConnectionMonitor::stopTimer); connect(&domainHandler, &DomainHandler::domainConnectionRefused, this, &ConnectionMonitor::stopTimer); connect(&domainHandler, &DomainHandler::redirectToErrorDomainURL, this, &ConnectionMonitor::stopTimer); + connect(&domainHandler, &DomainHandler::confirmConnectWithoutAvatarEntities, this, &ConnectionMonitor::stopTimer); connect(this, &ConnectionMonitor::setRedirectErrorState, &domainHandler, &DomainHandler::setRedirectErrorState); auto accountManager = DependencyManager::get(); connect(accountManager.data(), &AccountManager::loginComplete, this, &ConnectionMonitor::startTimer); diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 97ca59f617..8e92a434f5 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -1583,6 +1583,10 @@ void MyAvatar::addAvatarEntitiesToTree() { } } +bool MyAvatar::hasAvatarEntities() { + return _cachedAvatarEntityBlobs.count() > 0; +} + void MyAvatar::handleCanRezAvatarEntitiesChanged(bool canRezAvatarEntities) { if (canRezAvatarEntities) { // Start displaying avatar entities. diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index ee98e2e7a0..d1c6d5108b 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -1454,6 +1454,7 @@ public: void removeWornAvatarEntity(const EntityItemID& entityID); void clearWornAvatarEntities(); + bool hasAvatarEntities(); /**jsdoc * Checks whether your avatar is flying. diff --git a/libraries/networking/src/DomainHandler.cpp b/libraries/networking/src/DomainHandler.cpp index 6a47d74864..b3cd14d62c 100644 --- a/libraries/networking/src/DomainHandler.cpp +++ b/libraries/networking/src/DomainHandler.cpp @@ -126,6 +126,8 @@ void DomainHandler::hardReset(QString reason) { emit resetting(); softReset(reason); + _haveAskedConnectWithoutAvatarEntities = false; + _canConnectWithoutAvatarEntities = false; _isInErrorState = false; emit redirectErrorStateChanged(_isInErrorState); @@ -364,10 +366,14 @@ void DomainHandler::setIsConnected(bool isConnected) { _lastDomainConnectionError = -1; emit connectedToDomain(_domainURL); + // FIXME: Reinstate the requestDomainSettings() call here in version 2021.2.0 instead of having it in + // NodeList::processDomainServerList(). + /* if (_domainURL.scheme() == URL_SCHEME_HIFI && !_domainURL.host().isEmpty()) { // we've connected to new domain - time to ask it for global settings requestDomainSettings(); } + */ } else { emit disconnectedFromDomain(); @@ -375,6 +381,18 @@ void DomainHandler::setIsConnected(bool isConnected) { } } +void DomainHandler::setCanConnectWithoutAvatarEntities(bool canConnect) { + _canConnectWithoutAvatarEntities = canConnect; + _haveAskedConnectWithoutAvatarEntities = true; +} + +bool DomainHandler::canConnectWithoutAvatarEntities() { + if (!_canConnectWithoutAvatarEntities && !_haveAskedConnectWithoutAvatarEntities) { + emit confirmConnectWithoutAvatarEntities(); + } + return _canConnectWithoutAvatarEntities; +} + void DomainHandler::connectedToServerless(std::map namedPaths) { _namedPaths = namedPaths; setIsConnected(true); diff --git a/libraries/networking/src/DomainHandler.h b/libraries/networking/src/DomainHandler.h index 56d32d8609..923b5913e7 100644 --- a/libraries/networking/src/DomainHandler.h +++ b/libraries/networking/src/DomainHandler.h @@ -98,6 +98,7 @@ public: Node::LocalID getLocalID() const { return _localID; } void setLocalID(Node::LocalID localID) { _localID = localID; } + QString getScheme() const { return _domainURL.scheme(); } QString getHostname() const { return _domainURL.host(); } QUrl getErrorDomainURL(){ return _errorDomainURL; } @@ -133,6 +134,9 @@ public: bool isConnected() const { return _isConnected; } void setIsConnected(bool isConnected); + void setCanConnectWithoutAvatarEntities(bool canConnect); + bool canConnectWithoutAvatarEntities(); + bool isServerless() const { return _domainURL.scheme() != URL_SCHEME_HIFI; } bool getInterstitialModeEnabled() const; void setInterstitialModeEnabled(bool enableInterstitialMode); @@ -252,6 +256,7 @@ signals: void completedSocketDiscovery(); void resetting(); + void confirmConnectWithoutAvatarEntities(); void connectedToDomain(QUrl domainURL); void disconnectedFromDomain(); @@ -287,6 +292,8 @@ private: HifiSockAddr _iceServerSockAddr; NetworkPeer _icePeer; bool _isConnected { false }; + bool _haveAskedConnectWithoutAvatarEntities { false }; + bool _canConnectWithoutAvatarEntities { false }; bool _isInErrorState { false }; QJsonObject _settingsObject; QString _pendingPath; diff --git a/libraries/networking/src/NodeList.cpp b/libraries/networking/src/NodeList.cpp index 178566c7dc..344f5a8228 100644 --- a/libraries/networking/src/NodeList.cpp +++ b/libraries/networking/src/NodeList.cpp @@ -732,6 +732,11 @@ void NodeList::processDomainServerList(QSharedPointer message) // pull the permissions/right/privileges for this node out of the stream NodePermissions newPermissions; packetStream >> newPermissions; + // FIXME: Can remove this temporary work-around in version 2021.2.0. (New protocol version implies a domain server upgrade.) + // Adjust our canRezAvatarEntities permissions on older domains that do not have this setting. + // DomainServerList and DomainSettings packets can come in either order so need to adjust with both occurrences. + bool adjustedPermissions = adjustCanRezAvatarEntitiesPermissions(_domainHandler.getSettingsObject(), newPermissions, false); + // Is packet authentication enabled? bool isAuthenticated; packetStream >> isAuthenticated; @@ -787,7 +792,7 @@ void NodeList::processDomainServerList(QSharedPointer message) DependencyManager::get()->flagTimeForConnectionStep(LimitedNodeList::ConnectionStep::ReceiveDSList); if (_domainHandler.isConnected() && _domainHandler.getUUID() != domainUUID) { - // Recieved packet from different domain. + // Received packet from different domain. qWarning() << "IGNORING DomainList packet from" << domainUUID << "while connected to" << _domainHandler.getUUID() << ": sent " << pingLagTime << " msec ago."; qWarning(networking) << "DomainList request lag (interface->ds): " << domainServerRequestLag << "msec"; @@ -815,6 +820,23 @@ void NodeList::processDomainServerList(QSharedPointer message) setSessionLocalID(newLocalID); setSessionUUID(newUUID); + // FIXME: Remove this call to requestDomainSettings() and reinstate the one in DomainHandler::setIsConnected(), in version + // 2021.2.0. (New protocol version implies a domain server upgrade.) + if (!_domainHandler.isConnected() + && _domainHandler.getScheme() == URL_SCHEME_HIFI && !_domainHandler.getHostname().isEmpty()) { + // We're about to connect but we need the domain settings (in particular, the node permissions) in order to adjust the + // canRezAvatarEntities permission above before using the permissions in determining whether or not to connect without + // avatar entities rezzing below. + _domainHandler.requestDomainSettings(); + } + + // Don't continue login to the domain if have avatar entities and don't have permissions to rez them, unless user has OKed + // continuing login. + if (!newPermissions.can(NodePermissions::Permission::canRezAvatarEntities) + && (!adjustedPermissions || !_domainHandler.canConnectWithoutAvatarEntities())) { + return; + } + // if this was the first domain-server list from this domain, we've now connected if (!_domainHandler.isConnected()) { _domainHandler.setLocalID(domainLocalID); @@ -826,11 +848,6 @@ void NodeList::processDomainServerList(QSharedPointer message) DependencyManager::get()->lookupShareableNameForDomainID(domainUUID); } - // FIXME: Can remove this temporary work-around in version 2021.2.0. (New protocol version implies a domain server upgrade.) - // Adjust our canRezAvatarEntities permissions on older domains that do not have this setting. - // DomainServerList and DomainSettings packets can come in either order so need to adjust with both occurrences. - adjustCanRezAvatarEntitiesPermissions(_domainHandler.getSettingsObject(), newPermissions, false); - setPermissions(newPermissions); setAuthenticatePackets(isAuthenticated); @@ -1382,12 +1399,12 @@ void NodeList::startThread() { // FIXME: Can remove this work-around in version 2021.2.0. (New protocol version implies a domain server upgrade.) -void NodeList::adjustCanRezAvatarEntitiesPermissions(const QJsonObject& domainSettingsObject, +bool NodeList::adjustCanRezAvatarEntitiesPermissions(const QJsonObject& domainSettingsObject, NodePermissions& permissions, bool notify) { if (domainSettingsObject.isEmpty()) { // Don't have enough information to adjust yet. - return; + return false; // Failed to adjust. } const double CANREZAVATARENTITIES_INTRODUCED_VERSION = 2.5; @@ -1404,6 +1421,7 @@ void NodeList::adjustCanRezAvatarEntitiesPermissions(const QJsonObject& domainSe } } + return true; // Successfully adjusted. } // FIXME: Can remove this work-around in version 2021.2.0. (New protocol version implies a domain server upgrade.) diff --git a/libraries/networking/src/NodeList.h b/libraries/networking/src/NodeList.h index dab8e675dc..e850d2c07a 100644 --- a/libraries/networking/src/NodeList.h +++ b/libraries/networking/src/NodeList.h @@ -124,7 +124,7 @@ public slots: void processUsernameFromIDReply(QSharedPointer message); // FIXME: Can remove these work-arounds in version 2021.2.0. (New protocol version implies a domain server upgrade.) - void adjustCanRezAvatarEntitiesPermissions(const QJsonObject& domainSettingsObject, NodePermissions& permissions, + bool adjustCanRezAvatarEntitiesPermissions(const QJsonObject& domainSettingsObject, NodePermissions& permissions, bool notify); void adjustCanRezAvatarEntitiesPerSettings(const QJsonObject& domainSettingsObject);