diff --git a/assignment-client/src/avatars/AvatarMixer.cpp b/assignment-client/src/avatars/AvatarMixer.cpp index 3a5116a2e9..f218daed03 100644 --- a/assignment-client/src/avatars/AvatarMixer.cpp +++ b/assignment-client/src/avatars/AvatarMixer.cpp @@ -229,7 +229,7 @@ void AvatarMixer::start() { auto start = usecTimestampNow(); nodeList->nestedEach([&](NodeList::const_iterator cbegin, NodeList::const_iterator cend) { std::for_each(cbegin, cend, [&](const SharedNodePointer& node) { - if (node->getType() == NodeType::Agent && !node->isUpstream()) { + if (node->getType() == NodeType::Agent) { manageIdentityData(node); } @@ -325,8 +325,8 @@ void AvatarMixer::manageIdentityData(const SharedNodePointer& node) { sendIdentity = true; } } - if (sendIdentity) { - + if (sendIdentity && !node->isUpstream()) { + sendIdentityPacket(nodeData, node); // Tell node whose name changed about its new session display name or avatar. // since this packet includes a change to either the skeleton model URL or the display name // it needs a new sequence number nodeData->getAvatar().pushIdentitySequenceNumber(); diff --git a/assignment-client/src/avatars/AvatarMixerSlave.cpp b/assignment-client/src/avatars/AvatarMixerSlave.cpp index bbf68f0e22..4ff447a95a 100644 --- a/assignment-client/src/avatars/AvatarMixerSlave.cpp +++ b/assignment-client/src/avatars/AvatarMixerSlave.cpp @@ -81,7 +81,7 @@ int AvatarMixerSlave::sendIdentityPacket(const AvatarMixerClientData* nodeData, int AvatarMixerSlave::sendReplicatedIdentityPacket(const Node& agentNode, const AvatarMixerClientData* nodeData, const Node& destinationNode) { if (AvatarMixer::shouldReplicateTo(agentNode, destinationNode)) { - QByteArray individualData = nodeData->getConstAvatarData()->identityByteArray(); + QByteArray individualData = nodeData->getConstAvatarData()->identityByteArray(true); individualData.replace(0, NUM_BYTES_RFC4122_UUID, nodeData->getNodeID().toRfc4122()); // FIXME, this looks suspicious auto identityPacket = NLPacketList::create(PacketType::ReplicatedAvatarIdentity, QByteArray(), true, true); identityPacket->write(individualData); diff --git a/interface/resources/qml/hifi/NameCard.qml b/interface/resources/qml/hifi/NameCard.qml index 91c1d99cf5..65517f5a73 100644 --- a/interface/resources/qml/hifi/NameCard.qml +++ b/interface/resources/qml/hifi/NameCard.qml @@ -43,6 +43,7 @@ Item { property bool selected: false property bool isAdmin: false property bool isPresent: true + property bool isReplicated: false property string placeName: "" property string profilePicBorderColor: (connectionStatus == "connection" ? hifi.colors.indigoAccent : (connectionStatus == "friend" ? hifi.colors.greenHighlight : "transparent")) property alias avImage: avatarImage diff --git a/interface/resources/qml/hifi/Pal.qml b/interface/resources/qml/hifi/Pal.qml index bbb42e61ac..8db04a0f5b 100644 --- a/interface/resources/qml/hifi/Pal.qml +++ b/interface/resources/qml/hifi/Pal.qml @@ -473,6 +473,7 @@ Rectangle { visible: !isCheckBox && !isButton && !isAvgAudio; uuid: model ? model.sessionId : ""; selected: styleData.selected; + isReplicated: model.isReplicated; isAdmin: model && model.admin; isPresent: model && model.isPresent; // Size @@ -553,6 +554,7 @@ Rectangle { id: actionButton; color: 2; // Red visible: isButton; + enabled: !nameCard.isReplicated; anchors.centerIn: parent; width: 32; height: 32; diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index 4926ca0fdb..1147b8a811 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -1509,6 +1509,7 @@ void AvatarData::processAvatarIdentity(const QByteArray& identityData, bool& ide >> identity.attachmentData >> identity.displayName >> identity.sessionDisplayName + >> identity.isReplicated >> identity.avatarEntityData; // set the store identity sequence number to match the incoming identity @@ -1531,6 +1532,11 @@ void AvatarData::processAvatarIdentity(const QByteArray& identityData, bool& ide } maybeUpdateSessionDisplayNameFromTransport(identity.sessionDisplayName); + if (identity.isReplicated != _isReplicated) { + _isReplicated = identity.isReplicated; + identityChanged = true; + } + if (identity.attachmentData != _attachmentData) { setAttachmentData(identity.attachmentData); identityChanged = true; @@ -1561,7 +1567,7 @@ void AvatarData::processAvatarIdentity(const QByteArray& identityData, bool& ide } } -QByteArray AvatarData::identityByteArray() const { +QByteArray AvatarData::identityByteArray(bool setIsReplicated) const { QByteArray identityData; QDataStream identityStream(&identityData, QIODevice::Append); const QUrl& urlToSend = cannonicalSkeletonModelURL(emptyURL); // depends on _skeletonModelURL @@ -1576,6 +1582,7 @@ QByteArray AvatarData::identityByteArray() const { << _attachmentData << _displayName << getSessionDisplayNameForTransport() // depends on _sessionDisplayName + << (_isReplicated || setIsReplicated) << _avatarEntityData; }); diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index de905277d5..63bdd1112f 100644 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -531,6 +531,7 @@ public: QVector attachmentData; QString displayName; QString sessionDisplayName; + bool isReplicated; AvatarEntityMap avatarEntityData; }; @@ -539,7 +540,7 @@ public: void processAvatarIdentity(const QByteArray& identityData, bool& identityChanged, bool& displayNameChanged, bool& skeletonModelUrlChanged); - QByteArray identityByteArray() const; + QByteArray identityByteArray(bool setIsReplicated = false) const; const QUrl& getSkeletonModelURL() const { return _skeletonModelURL; } const QString& getDisplayName() const { return _displayName; } @@ -629,6 +630,8 @@ public: float getDensity() const { return _density; } + bool getIsReplicated() const { return _isReplicated; } + signals: void displayNameChanged(); @@ -665,6 +668,10 @@ protected: bool hasParent() const { return !getParentID().isNull(); } bool hasFaceTracker() const { return _headData ? _headData->_isFaceTrackerConnected : false; } + // isReplicated will be true on downstream Avatar Mixers and their clients, but false on the upstream "master" + // Audio Mixer that the replicated avatar is connected to. + bool _isReplicated{ false }; + glm::vec3 _handPosition; virtual const QString& getSessionDisplayNameForTransport() const { return _sessionDisplayName; } virtual void maybeUpdateSessionDisplayNameFromTransport(const QString& sessionDisplayName) { } // No-op in AvatarMixer diff --git a/libraries/avatars/src/ScriptAvatarData.cpp b/libraries/avatars/src/ScriptAvatarData.cpp index 01d7f293d8..90ec7ec309 100644 --- a/libraries/avatars/src/ScriptAvatarData.cpp +++ b/libraries/avatars/src/ScriptAvatarData.cpp @@ -152,6 +152,15 @@ QString ScriptAvatarData::getSessionDisplayName() const { return QString(); } } + +bool ScriptAvatarData::getIsReplicated() const { + if (AvatarSharedPointer sharedAvatarData = _avatarData.lock()) { + return sharedAvatarData->getIsReplicated(); + } else { + return false; + } +} + // // IDENTIFIER PROPERTIES // END diff --git a/libraries/avatars/src/ScriptAvatarData.h b/libraries/avatars/src/ScriptAvatarData.h index d763b6e97a..1b6944e01d 100644 --- a/libraries/avatars/src/ScriptAvatarData.h +++ b/libraries/avatars/src/ScriptAvatarData.h @@ -45,6 +45,7 @@ class ScriptAvatarData : public QObject { Q_PROPERTY(QUuid sessionUUID READ getSessionUUID) Q_PROPERTY(QString displayName READ getDisplayName NOTIFY displayNameChanged) Q_PROPERTY(QString sessionDisplayName READ getSessionDisplayName) + Q_PROPERTY(bool isReplicated READ getIsReplicated) // // ATTACHMENT AND JOINT PROPERTIES @@ -95,6 +96,7 @@ public: QUuid getSessionUUID() const; QString getDisplayName() const; QString getSessionDisplayName() const; + bool getIsReplicated() const; // // ATTACHMENT AND JOINT PROPERTIES diff --git a/libraries/networking/src/udt/PacketHeaders.cpp b/libraries/networking/src/udt/PacketHeaders.cpp index ad15d04db1..240697d890 100644 --- a/libraries/networking/src/udt/PacketHeaders.cpp +++ b/libraries/networking/src/udt/PacketHeaders.cpp @@ -69,7 +69,7 @@ PacketVersion versionForPacketType(PacketType packetType) { case PacketType::AvatarData: case PacketType::BulkAvatarData: case PacketType::KillAvatar: - return static_cast(AvatarMixerPacketVersion::AvatarIdentitySequenceFront); + return static_cast(AvatarMixerPacketVersion::IsReplicatedInAvatarIdentity); case PacketType::MessagesData: return static_cast(MessageDataVersion::TextOrBinaryData); case PacketType::ICEServerHeartbeat: diff --git a/libraries/networking/src/udt/PacketHeaders.h b/libraries/networking/src/udt/PacketHeaders.h index 2944c1ce93..6c42193e11 100644 --- a/libraries/networking/src/udt/PacketHeaders.h +++ b/libraries/networking/src/udt/PacketHeaders.h @@ -247,7 +247,8 @@ enum class AvatarMixerPacketVersion : PacketVersion { IdentityPacketsIncludeUpdateTime, AvatarIdentitySequenceId, MannequinDefaultAvatar, - AvatarIdentitySequenceFront + AvatarIdentitySequenceFront, + IsReplicatedInAvatarIdentity }; enum class DomainConnectRequestVersion : PacketVersion { diff --git a/scripts/system/pal.js b/scripts/system/pal.js index 0500c13f9b..6c1652c700 100644 --- a/scripts/system/pal.js +++ b/scripts/system/pal.js @@ -479,7 +479,8 @@ function populateNearbyUserList(selectData, oldAudioData) { admin: false, personalMute: !!id && Users.getPersonalMuteStatus(id), // expects proper boolean, not null ignore: !!id && Users.getIgnoreStatus(id), // ditto - isPresent: true + isPresent: true, + isReplicated: avatar.isReplicated }; if (id) { addAvatarNode(id); // No overlay for ourselves