diff --git a/assignment-client/src/avatars/MixerAvatar.cpp b/assignment-client/src/avatars/MixerAvatar.cpp index 2d9361d767..6ef73b15e7 100644 --- a/assignment-client/src/avatars/MixerAvatar.cpp +++ b/assignment-client/src/avatars/MixerAvatar.cpp @@ -9,6 +9,8 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +#include "MixerAvatar.h" + #include #include #include @@ -22,11 +24,13 @@ #include #include #include -#include "MixerAvatar.h" +#include "ClientTraitsHandler.h" #include "AvatarLogging.h" +const QString MixerAvatar::VERIFY_FAIL_MODEL { "qrc:/meshes/verifyFailed.fst" }; + void MixerAvatar::fetchAvatarFST() { - _verifyState = kNoncertified; + _verifyState = nonCertified; _certificateIdFromURL.clear(); _certificateIdFromFST.clear(); _marketplaceIdFromURL.clear(); @@ -60,11 +64,11 @@ void MixerAvatar::fetchAvatarFST() { } _avatarRequest = fstRequest; connect(fstRequest, &ResourceRequest::finished, this, &MixerAvatar::fstRequestComplete); - _verifyState = kRequestingFST; + _verifyState = requestingFST; fstRequest->send(); } else { qCDebug(avatars) << "Couldn't create FST request for" << avatarURL; - _verifyState = kError; + _verifyState = error; } } @@ -74,11 +78,11 @@ void MixerAvatar::fstRequestComplete() { if (fstRequest == _avatarRequest) { auto result = fstRequest->getResult(); if (result != ResourceRequest::Success) { - _verifyState = kError; + _verifyState = error; qCDebug(avatars) << "FST request for" << fstRequest->getUrl() << "failed:" << result; } else { _avatarFSTContents = fstRequest->getData(); - _verifyState = kReceivedFST; + _verifyState = receivedFST; _pendingEvent = true; } _avatarRequest->deleteLater(); @@ -101,8 +105,8 @@ bool MixerAvatar::generateFSTHash() { bool MixerAvatar::validateFSTHash(const QString& publicKey) { // Guess we should refactor this stuff into a Authorization namespace ... -return EntityItemProperties::verifySignature(publicKey, _certificateHash, - QByteArray::fromBase64(_certificateIdFromFST.toUtf8())); + return EntityItemProperties::verifySignature(publicKey, _certificateHash, + QByteArray::fromBase64(_certificateIdFromFST.toUtf8())); } QByteArray MixerAvatar::canonicalJson(const QString fstFile) { @@ -164,14 +168,14 @@ void MixerAvatar::ownerRequestComplete() { if (networkReply->error() == QNetworkReply::NoError) { _dynamicMarketResponse = networkReply->readAll(); - _verifyState = kOwnerResponse; + _verifyState = ownerResponse; _pendingEvent = true; } else { auto jsonData = QJsonDocument::fromJson(networkReply->readAll())["data"]; if (!jsonData.isUndefined() && !jsonData.toObject()["message"].isUndefined()) { qCDebug(avatars) << "Owner lookup failed for" << getDisplayName() << ":" << jsonData.toObject()["message"].toString(); - _verifyState = kError; + _verifyState = error; } } networkReply->deleteLater(); @@ -185,115 +189,116 @@ void MixerAvatar::processCertifyEvents() { QMutexLocker certifyLocker(&_avatarCertifyLock); switch (_verifyState) { - case kReceivedFST: - { - generateFSTHash(); - QString& marketplacePublicKey = EntityItem::_marketplacePublicKey; - bool staticVerification = validateFSTHash(marketplacePublicKey); - _verifyState = staticVerification ? kStaticValidation : kVerificationFailed; + case receivedFST: + { + generateFSTHash(); + QString& marketplacePublicKey = EntityItem::_marketplacePublicKey; + bool staticVerification = validateFSTHash(marketplacePublicKey); + _verifyState = staticVerification ? staticValidation : verificationFailed; - if (_verifyState == kStaticValidation) { - static const QString POP_MARKETPLACE_API { "/api/v1/commerce/proof_of_purchase_status/transfer" }; - auto& networkAccessManager = NetworkAccessManager::getInstance(); - QNetworkRequest networkRequest; - networkRequest.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); - networkRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); - QUrl requestURL = NetworkingConstants::METAVERSE_SERVER_URL(); - requestURL.setPath(POP_MARKETPLACE_API); - networkRequest.setUrl(requestURL); + if (_verifyState == staticValidation) { + static const QString POP_MARKETPLACE_API { "/api/v1/commerce/proof_of_purchase_status/transfer" }; + auto& networkAccessManager = NetworkAccessManager::getInstance(); + QNetworkRequest networkRequest; + networkRequest.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); + networkRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); + QUrl requestURL = NetworkingConstants::METAVERSE_SERVER_URL(); + requestURL.setPath(POP_MARKETPLACE_API); + networkRequest.setUrl(requestURL); - QJsonObject request; - request["certificate_id"] = _certificateIdFromFST; - _verifyState = kRequestingOwner; - QNetworkReply* networkReply = networkAccessManager.put(networkRequest, QJsonDocument(request).toJson()); - connect(networkReply, &QNetworkReply::finished, this, &MixerAvatar::ownerRequestComplete); - } else { - _needsIdentityUpdate = true; - _pendingEvent = false; - qCDebug(avatars) << "Avatar" << getDisplayName() << "FAILED static certification"; - } - break; - } - - case kOwnerResponse: - { - QJsonDocument responseJson = QJsonDocument::fromJson(_dynamicMarketResponse.toUtf8()); - QString ownerPublicKey; - bool ownerValid = false; - qCDebug(avatars) << "Marketplace response for avatar" << getDisplayName() << ":" << _dynamicMarketResponse; - if (responseJson["status"].toString() == "success") { - QJsonValue jsonData = responseJson["data"]; - if (jsonData.isObject()) { - auto ownerJson = jsonData["transfer_recipient_key"]; - if (ownerJson.isString()) { - ownerPublicKey = ownerJson.toString(); - } - auto transferStatusJson = jsonData["transfer_status"]; - if (transferStatusJson.isArray() && transferStatusJson.toArray()[0].toString() == "confirmed") { - ownerValid = true; - } - } - if (ownerValid && !ownerPublicKey.isEmpty()) { - if (ownerPublicKey.startsWith("-----BEGIN ")){ - _ownerPublicKey = ownerPublicKey; - } else { - _ownerPublicKey = "-----BEGIN PUBLIC KEY-----\n" - + ownerPublicKey - + "\n-----END PUBLIC KEY-----\n"; - } - sendOwnerChallenge(); - _verifyState = kChallengeClient; + QJsonObject request; + request["certificate_id"] = _certificateIdFromFST; + _verifyState = requestingOwner; + QNetworkReply* networkReply = networkAccessManager.put(networkRequest, QJsonDocument(request).toJson()); + connect(networkReply, &QNetworkReply::finished, this, &MixerAvatar::ownerRequestComplete); } else { - _verifyState = kError; + _needsIdentityUpdate = true; + _pendingEvent = false; + qCDebug(avatars) << "Avatar" << getDisplayName() << "FAILED static certification"; } - } else { - qCDebug(avatars) << "Get owner status failed for " << getDisplayName() << _marketplaceIdFromURL << - "message:" << responseJson["message"].toString(); - _verifyState = kError; - } - _pendingEvent = false; - break; - } - - case kChallengeResponse: - { - if (_challengeResponse.length() < 8) { - _verifyState = kError; break; } - int avatarIDLength; - int signedNonceLength; + case ownerResponse: { - QDataStream responseStream(_challengeResponse); - responseStream.setByteOrder(QDataStream::LittleEndian); - responseStream >> avatarIDLength >> signedNonceLength; + QJsonDocument responseJson = QJsonDocument::fromJson(_dynamicMarketResponse.toUtf8()); + QString ownerPublicKey; + bool ownerValid = false; + qCDebug(avatars) << "Marketplace response for avatar" << getDisplayName() << ":" << _dynamicMarketResponse; + if (responseJson["status"].toString() == "success") { + QJsonValue jsonData = responseJson["data"]; + if (jsonData.isObject()) { + auto ownerJson = jsonData["transfer_recipient_key"]; + if (ownerJson.isString()) { + ownerPublicKey = ownerJson.toString(); + } + auto transferStatusJson = jsonData["transfer_status"]; + if (transferStatusJson.isArray() && transferStatusJson.toArray()[0].toString() == "confirmed") { + ownerValid = true; + } + } + if (ownerValid && !ownerPublicKey.isEmpty()) { + if (ownerPublicKey.startsWith("-----BEGIN ")){ + _ownerPublicKey = ownerPublicKey; + } else { + _ownerPublicKey = "-----BEGIN PUBLIC KEY-----\n" + + ownerPublicKey + + "\n-----END PUBLIC KEY-----\n"; + } + sendOwnerChallenge(); + _verifyState = challengeClient; + } else { + _verifyState = error; + } + } else { + qCDebug(avatars) << "Get owner status failed for " << getDisplayName() << _marketplaceIdFromURL << + "message:" << responseJson["message"].toString(); + _verifyState = error; + } + _pendingEvent = false; + break; } - QByteArray avatarID(_challengeResponse.data() + 2 * sizeof(int), avatarIDLength); - QByteArray signedNonce(_challengeResponse.data() + 2 * sizeof(int) + avatarIDLength, signedNonceLength); - bool challengeResult = EntityItemProperties::verifySignature(_ownerPublicKey, _challengeNonceHash, - QByteArray::fromBase64(signedNonce)); - _verifyState = challengeResult ? kVerificationSucceeded : kVerificationFailed; - _needsIdentityUpdate = true; - if (_verifyState == kVerificationFailed) { - qCDebug(avatars) << "Dynamic verification FAILED for " << getDisplayName() << getSessionUUID(); - } else { - qCDebug(avatars) << "Dynamic verification SUCCEEDED for " << getDisplayName() << getSessionUUID(); + case challengeResponse: + { + if (_challengeResponse.length() < 8) { + _verifyState = error; + break; + } + + int avatarIDLength; + int signedNonceLength; + { + QDataStream responseStream(_challengeResponse); + responseStream.setByteOrder(QDataStream::LittleEndian); + responseStream >> avatarIDLength >> signedNonceLength; + } + QByteArray avatarID(_challengeResponse.data() + 2 * sizeof(int), avatarIDLength); + QByteArray signedNonce(_challengeResponse.data() + 2 * sizeof(int) + avatarIDLength, signedNonceLength); + + bool challengeResult = EntityItemProperties::verifySignature(_ownerPublicKey, _challengeNonceHash, + QByteArray::fromBase64(signedNonce)); + _verifyState = challengeResult ? verificationSucceeded : verificationFailed; + _needsIdentityUpdate = true; + if (_verifyState == verificationFailed) { + qCDebug(avatars) << "Dynamic verification FAILED for " << getDisplayName() << getSessionUUID(); + setSkeletonModelURL(QUrl(VERIFY_FAIL_MODEL)); + } else { + qCDebug(avatars) << "Dynamic verification SUCCEEDED for " << getDisplayName() << getSessionUUID(); + } + _pendingEvent = false; + break; } - _pendingEvent = false; - break; - } - case kRequestingOwner: - { // Qt networking done on this thread: - QCoreApplication::processEvents(); - break; - } + case requestingOwner: + { // Qt networking done on this thread: + QCoreApplication::processEvents(); + break; + } - default: - qCDebug(avatars) << "Unexpected verify state" << _verifyState; - break; + default: + qCDebug(avatars) << "Unexpected verify state" << _verifyState; + break; } // close switch } @@ -318,19 +323,20 @@ void MixerAvatar::sendOwnerChallenge() { static constexpr int CHALLENGE_TIMEOUT_MS = 10 * 1000; // 10 s _challengeTimeout.setInterval(CHALLENGE_TIMEOUT_MS); _challengeTimeout.connect(&_challengeTimeout, &QTimer::timeout, [this]() { - _verifyState = kVerificationFailed; + _verifyState = verificationFailed; + setSkeletonModelURL(QUrl(VERIFY_FAIL_MODEL)); _needsIdentityUpdate = true; - }); + }); } -void MixerAvatar::handleChallengeResponse(ReceivedMessage * response) { +void MixerAvatar::handleChallengeResponse(ReceivedMessage* response) { QByteArray avatarID; QByteArray encryptedNonce; QMutexLocker certifyLocker(&_avatarCertifyLock); - if (_verifyState == kChallengeClient) { + if (_verifyState == challengeClient) { _challengeTimeout.stop(); _challengeResponse = response->readAll(); - _verifyState = kChallengeResponse; + _verifyState = challengeResponse; _pendingEvent = true; } } diff --git a/assignment-client/src/avatars/MixerAvatar.h b/assignment-client/src/avatars/MixerAvatar.h index 168fc742b7..528943df24 100644 --- a/assignment-client/src/avatars/MixerAvatar.h +++ b/assignment-client/src/avatars/MixerAvatar.h @@ -25,21 +25,21 @@ public: void setNeedsHeroCheck(bool needsHeroCheck = true) { _needsHeroCheck = needsHeroCheck; } void fetchAvatarFST(); - virtual bool isCertifyFailed() const override { return _verifyState == kVerificationFailed; } + virtual bool isCertifyFailed() const override { return _verifyState == verificationFailed; } bool needsIdentityUpdate() const { return _needsIdentityUpdate; } void clearIdentityUpdate() { _needsIdentityUpdate = false; } void processCertifyEvents(); - void handleChallengeResponse(ReceivedMessage * response); + void handleChallengeResponse(ReceivedMessage* response); private: bool _needsHeroCheck { false }; // Avatar certification/verification: - enum VerifyState { kNoncertified, kRequestingFST, kReceivedFST, kStaticValidation, kRequestingOwner, kOwnerResponse, - kChallengeClient, kChallengeResponse, kVerified, kVerificationFailed, kVerificationSucceeded, kError }; + enum VerifyState { nonCertified, requestingFST, receivedFST, staticValidation, requestingOwner, ownerResponse, + challengeClient, challengeResponse, verified, verificationFailed, verificationSucceeded, error }; Q_ENUM(VerifyState); - VerifyState _verifyState { kNoncertified }; + VerifyState _verifyState { nonCertified }; std::atomic _pendingEvent { false }; QMutex _avatarCertifyLock; ResourceRequest* _avatarRequest { nullptr }; @@ -61,6 +61,8 @@ private: QByteArray canonicalJson(const QString fstFile); void sendOwnerChallenge(); + static const QString VERIFY_FAIL_MODEL; + private slots: void fstRequestComplete(); void ownerRequestComplete(); diff --git a/interface/resources/meshes/mannequin/man_stolen.fbx b/interface/resources/meshes/mannequin/man_stolen.fbx new file mode 100644 index 0000000000..739c0efe91 Binary files /dev/null and b/interface/resources/meshes/mannequin/man_stolen.fbx differ diff --git a/interface/resources/meshes/verifyFailed.fst b/interface/resources/meshes/verifyFailed.fst new file mode 100644 index 0000000000..97941bf836 --- /dev/null +++ b/interface/resources/meshes/verifyFailed.fst @@ -0,0 +1,86 @@ +name = mannequin2 +type = body+head +scale = 1 +filename = mannequin/man_stolen.fbx +texdir = textures +joint = jointEyeLeft = LeftEye +joint = jointRightHand = RightHand +joint = jointHead = Head +joint = jointEyeRight = RightEye +joint = jointNeck = Neck +joint = jointLeftHand = LeftHand +joint = jointLean = Spine +joint = jointRoot = Hips +freeJoint = LeftArm +freeJoint = LeftForeArm +freeJoint = RightArm +freeJoint = RightForeArm +jointIndex = RightHandPinky2 = 19 +jointIndex = LeftHandPinky3 = 44 +jointIndex = RightToeBase = 9 +jointIndex = LeftHandRing4 = 49 +jointIndex = LeftHandPinky1 = 42 +jointIndex = LeftHandRing1 = 46 +jointIndex = LeftLeg = 2 +jointIndex = RightHandIndex4 = 29 +jointIndex = LeftHandRing3 = 48 +jointIndex = RightShoulder = 14 +jointIndex = RightArm = 15 +jointIndex = Neck = 62 +jointIndex = RightHandMiddle2 = 35 +jointIndex = HeadTop_End = 66 +jointIndex = LeftHandRing2 = 47 +jointIndex = RightHandThumb1 = 30 +jointIndex = RightHandRing3 = 24 +jointIndex = LeftHandIndex3 = 52 +jointIndex = LeftForeArm = 40 +jointIndex = face = 68 +jointIndex = LeftToe_End = 5 +jointIndex = RightHandThumb3 = 32 +jointIndex = RightEye = 65 +jointIndex = Spine = 11 +jointIndex = LeftEye = 64 +jointIndex = LeftToeBase = 4 +jointIndex = LeftHandIndex4 = 53 +jointIndex = RightHandPinky4 = 21 +jointIndex = RightHandMiddle1 = 34 +jointIndex = Spine1 = 12 +jointIndex = LeftHandIndex2 = 51 +jointIndex = RightToe_End = 10 +jointIndex = RightHand = 17 +jointIndex = LeftUpLeg = 1 +jointIndex = RightHandRing1 = 22 +jointIndex = RightUpLeg = 6 +jointIndex = RightHandMiddle4 = 37 +jointIndex = Head = 63 +jointIndex = RightHandMiddle3 = 36 +jointIndex = RightHandIndex1 = 26 +jointIndex = LeftHandMiddle4 = 61 +jointIndex = LeftHandPinky4 = 45 +jointIndex = Hips = 0 +jointIndex = body = 67 +jointIndex = RightHandThumb2 = 31 +jointIndex = LeftHandThumb2 = 55 +jointIndex = RightHandThumb4 = 33 +jointIndex = RightHandPinky3 = 20 +jointIndex = LeftHandPinky2 = 43 +jointIndex = LeftShoulder = 38 +jointIndex = RightHandIndex3 = 28 +jointIndex = LeftHandThumb4 = 57 +jointIndex = RightLeg = 7 +jointIndex = RightHandIndex2 = 27 +jointIndex = LeftHandMiddle3 = 60 +jointIndex = RightHandRing4 = 25 +jointIndex = LeftHandThumb1 = 54 +jointIndex = LeftArm = 39 +jointIndex = LeftHandThumb3 = 56 +jointIndex = LeftHandMiddle1 = 58 +jointIndex = RightHandPinky1 = 18 +jointIndex = Spine2 = 13 +jointIndex = RightHandRing2 = 23 +jointIndex = RightForeArm = 16 +jointIndex = LeftHandIndex1 = 50 +jointIndex = RightFoot = 8 +jointIndex = LeftHandMiddle2 = 59 +jointIndex = LeftHand = 41 +jointIndex = LeftFoot = 3 diff --git a/interface/resources/qml/AvatarTheft.qml b/interface/resources/qml/AvatarTheft.qml deleted file mode 100644 index 5c67e64589..0000000000 --- a/interface/resources/qml/AvatarTheft.qml +++ /dev/null @@ -1,70 +0,0 @@ -import QtQuick 2.7 -import stylesUit 1.0 as HifiStylesUit -import controlsUit 1.0 as HifiControlsUit - -Rectangle { - color: "black" - height: 480 - width: 720 - - readonly property string avatarTheftEntityName: "hifi-avatar-theft-banner"; - - HifiStylesUit.RalewayRegular { - id: displayMessage; - text: "The avatar you're using is registered to another user."; - size: 20; - color: "white"; - anchors.top: parent.top; - anchors.topMargin: 0.1 * parent.height; - anchors.left: parent.left; - anchors.leftMargin: (parent.width - width) / 2 - - verticalAlignment: Text.AlignVCenter - horizontalAlignment: Text.AlignHCenter - } - - HifiStylesUit.ShortcutText { - id: gotoShortcut; - anchors.top: parent.top; - anchors.topMargin: 0.4 * parent.height; - anchors.left: parent.left; - anchors.leftMargin: (parent.width - width) / 2 - font.family: "Raleway" - font.pixelSize: 20 - - verticalAlignment: Text.AlignVCenter - horizontalAlignment: Text.AlignHCenter - linkColor: hifi.colors.blueAccent - text: "Click here to change your avatar and dismiss this banner." - onLinkActivated: { - AddressManager.handleLookupString("hifi://BodyMart"); - } - } - - HifiStylesUit.RalewayRegular { - id: contactText; - text: "If you own this avatar, please contact" - size: 20; - anchors.bottom: parent.bottom - anchors.bottomMargin: 0.15 * parent.height - anchors.left: parent.left; - anchors.leftMargin: (parent.width - width) / 2 - } - - HifiStylesUit.ShortcutText { - id: gotoShortcut; - anchors.top: contactText.bottom; - anchors.left: parent.left; - anchors.leftMargin: (parent.width - width) / 2 - font.family: "Raleway" - font.pixelSize: 20 - - verticalAlignment: Text.AlignVCenter - horizontalAlignment: Text.AlignHCenter - linkColor: hifi.colors.blueAccent - text: "Click here to change your avatar and dismiss this banner." - onLinkActivated: { - HiFiAbout.openUrl("mailto:support@highfidelity.com"); - } - } -} diff --git a/interface/resources/qml/AvatarTheftBanner.qml b/interface/resources/qml/AvatarTheftBanner.qml deleted file mode 100644 index e2c437487e..0000000000 --- a/interface/resources/qml/AvatarTheftBanner.qml +++ /dev/null @@ -1,67 +0,0 @@ -import QtQuick 2.7 -import stylesUit 1.0 as HifiStylesUit -import controlsUit 1.0 as HifiControlsUit - -Rectangle { - color: "black" - - HifiStylesUit.HifiConstants { id: hifi; } - - HifiStylesUit.RalewayRegular { - id: displayMessage; - text: "The avatar you're using is registered to another user."; - size: 20; - color: "white"; - anchors.top: parent.top; - anchors.topMargin: 0.1 * parent.height; - anchors.left: parent.left; - anchors.leftMargin: (parent.width - width) / 2 - - verticalAlignment: Text.AlignVCenter - horizontalAlignment: Text.AlignHCenter - } - - HifiStylesUit.ShortcutText { - id: gotoShortcut; - anchors.top: parent.top; - anchors.topMargin: 0.4 * parent.height; - anchors.left: parent.left; - anchors.leftMargin: (parent.width - width) / 2 - font.family: "Raleway" - font.pixelSize: 20 - - verticalAlignment: Text.AlignVCenter - horizontalAlignment: Text.AlignHCenter - linkColor: hifi.colors.blueAccent - text: "Click here to change your avatar and dismiss this banner." - onLinkActivated: { - var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); - tablet.loadQMLSource("hifi/AvatarApp.qml"); - } - } - - HifiStylesUit.RalewayRegular { - id: contactText; - text: "If you own this avatar, please contact" - size: 20; - color: "white" - anchors.bottom: parent.bottom - anchors.bottomMargin: 0.15 * parent.height - anchors.left: parent.left; - anchors.leftMargin: (parent.width - width) / 2 - } - - HifiStylesUit.ShortcutText { - id: email; - anchors.top: contactText.bottom; - anchors.left: parent.left; - anchors.leftMargin: (parent.width - width) / 2 - font.family: "Raleway" - font.pixelSize: 20 - - verticalAlignment: Text.AlignVCenter - horizontalAlignment: Text.AlignHCenter - linkColor: hifi.colors.blueAccent - text: "support@highfidelity.com." - } -} diff --git a/interface/resources/qml/AvatarTheftSettings.qml b/interface/resources/qml/AvatarTheftSettings.qml deleted file mode 100644 index 849e611af2..0000000000 --- a/interface/resources/qml/AvatarTheftSettings.qml +++ /dev/null @@ -1,139 +0,0 @@ -import QtQuick 2.10 -import QtQuick.Controls 2.2 -import QtQuick.Layouts 1.3 - -import stylesUit 1.0 -import controlsUit 1.0 as HifiControlsUit - -Rectangle { - id: root; - - HifiConstants { id: hifi; } - - color: hifi.colors.baseGray; - - signal sendToScript(var message); - function emitSendToScript(message) { - sendToScript(message); - } - - function fromScript(message) { - } - - RalewayRegular { - id: title; - color: hifi.colors.white; - text: qsTr("Avatar Theft Entity position") - size: 20 - font.bold: true - anchors { - top: parent.top - left: parent.left - leftMargin: (parent.width - width) / 2 - } - } - - HifiControlsUit.Slider { - id: xSlider - anchors { - top: title.bottom - topMargin: 50 - left: parent.left - leftMargin: 20 - } - label: "X OFFSET: " + value.toFixed(2); - maximumValue: 1.0 - minimumValue: -1.0 - stepSize: 0.05 - value: 0.0 - width: 300 - onValueChanged: { - emitSendToScript({ - "method": "reposition", - "x": value - }); - } - } - - HifiControlsUit.Slider { - id: ySlider - anchors { - top: xSlider.bottom - topMargin: 50 - left: parent.left - leftMargin: 20 - } - label: "Y OFFSET: " + value.toFixed(2); - maximumValue: 1.0 - minimumValue: -1.0 - stepSize: 0.05 - value: 0.0 - width: 300 - onValueChanged: { - emitSendToScript({ - "method": "reposition", - "y": value - }); - } - } - - HifiControlsUit.Slider { - id: zSlider - anchors { - top: ySlider.bottom - topMargin: 50 - left: parent.left - leftMargin: 20 - } - label: "Z OFFSET: " + value.toFixed(2); - maximumValue: 0.0 - minimumValue: -2.0 - stepSize: 0.05 - value: -1.0 - width: 300 - onValueChanged: { - emitSendToScript({ - "method": "reposition", - "z": value - }); - } - } - - HifiControlsUit.Button { - id: setVisibleButton; - text: setVisible ? "SET INVISIBLE" : "SET VISIBLE"; - width: 300; - property bool setVisible: true; - anchors { - top: zSlider.bottom - topMargin: 50 - left: parent.left - leftMargin: 20 - } - onClicked: { - setVisible = !setVisible; - emitSendToScript({ - "method": "setVisible", - "visible": setVisible - }); - } - } - - HifiControlsUit.Button { - id: printButton; - text: "PRINT POSITIONS"; - width: 300; - anchors { - top: setVisibleButton.bottom - topMargin: 50 - left: parent.left - leftMargin: 20 - } - onClicked: { - emitSendToScript({ - "method": "print", - }); - } - } -} - diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index da69afd923..08cd82b838 100755 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -180,7 +180,7 @@ void AvatarManager::updateMyAvatar(float deltaTime) { static AvatarCertifyBanner theftBanner; if (_myAvatar->isCertifyFailed()) { - theftBanner.show(_myAvatar->getSessionUUID(), _myAvatar->getJointIndex("_CAMERA_MATRIX")); + theftBanner.show(_myAvatar->getSessionUUID()); } else { theftBanner.clear(); } diff --git a/interface/src/ui/AvatarCertifyBanner.cpp b/interface/src/ui/AvatarCertifyBanner.cpp index 6099f40c94..e40da46805 100644 --- a/interface/src/ui/AvatarCertifyBanner.cpp +++ b/interface/src/ui/AvatarCertifyBanner.cpp @@ -9,45 +9,49 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -#include - -#include "OffscreenQmlElement.h" -#include "EntityTreeRenderer.h" #include "AvatarCertifyBanner.h" -static const QUrl AVATAR_THEFT_DIALOG = PathUtils::qmlUrl("AvatarTheftSettings.qml"); -static const QUrl AVATAR_THEFT_BANNER_IMAGE = PathUtils::resourcesUrl("images/AvatarTheftBanner.png"); +#include -AvatarCertifyBanner::AvatarCertifyBanner(QQuickItem* parent) { +#include "ui/TabletScriptingInterface.h" +#include "EntityTreeRenderer.h" +namespace { + const QUrl AVATAR_THEFT_BANNER_IMAGE = PathUtils::resourcesUrl("images/AvatarTheftBanner.png"); + const QString AVATAR_THEFT_BANNER_SCRIPT { "/system/clickToAvatarApp.js" }; } -AvatarCertifyBanner::~AvatarCertifyBanner() -{ } +AvatarCertifyBanner::AvatarCertifyBanner(QQuickItem* parent) { +} -void AvatarCertifyBanner::show(const QUuid& avatarID, int jointIndex) { +void AvatarCertifyBanner::show(const QUuid& avatarID) { if (!_active) { auto entityTreeRenderer = DependencyManager::get(); EntityTreePointer entityTree = entityTreeRenderer->getTree(); if (!entityTree) { return; } - glm::vec3 position { 0.0f, 0.0f, -0.7f }; + const bool tabletShown = DependencyManager::get()->property("tabletShown").toBool(); + const auto& position = tabletShown ? glm::vec3(0.0f, 0.0f, -1.8f) : glm::vec3(0.0f, 0.0f, -0.7f); + const float scaleFactor = tabletShown ? 2.6f : 1.0f; + EntityItemProperties entityProperties; entityProperties.setType(EntityTypes::Image); entityProperties.setImageURL(AVATAR_THEFT_BANNER_IMAGE.toString()); - entityProperties.setName("hifi-avatar-theft-banner"); + entityProperties.setName("hifi-avatar-notification-banner"); entityProperties.setParentID(avatarID); entityProperties.setParentJointIndex(CAMERA_MATRIX_INDEX); entityProperties.setLocalPosition(position); - entityProperties.setDimensions({ 1.0f, 1.0f, 0.3f }); - entityProperties.setRenderLayer(RenderLayer::WORLD); + entityProperties.setDimensions(glm::vec3(1.0f, 1.0f, 0.3f) * scaleFactor); + entityProperties.setRenderLayer(tabletShown ? RenderLayer::WORLD : RenderLayer::FRONT); entityProperties.getGrab().setGrabbable(false); + QString scriptPath = QUrl(PathUtils::defaultScriptsLocation("")).toString() + AVATAR_THEFT_BANNER_SCRIPT; + entityProperties.setScript(scriptPath); entityProperties.setVisible(true); entityTree->withWriteLock([&] { auto entityTreeItem = entityTree->addEntity(_bannerID, entityProperties); - entityTreeItem->setLocalPosition(position); // ?! + entityTreeItem->setLocalPosition(position); }); _active = true; diff --git a/interface/src/ui/AvatarCertifyBanner.h b/interface/src/ui/AvatarCertifyBanner.h index 4bc4f30653..c9bb23cb96 100644 --- a/interface/src/ui/AvatarCertifyBanner.h +++ b/interface/src/ui/AvatarCertifyBanner.h @@ -12,13 +12,18 @@ #ifndef hifi_AvatarCertifyBanner_h #define hifi_AvatarCertifyBanner_h +#include +#include "OffscreenQmlElement.h" +#include "EntityItemID.h" + +class EntityItemID; + class AvatarCertifyBanner : QObject { Q_OBJECT HIFI_QML_DECL public: AvatarCertifyBanner(QQuickItem* parent = nullptr); - ~AvatarCertifyBanner(); - void show(const QUuid& avatarID, int jointIndex); + void show(const QUuid& avatarID); void clear(); private: diff --git a/libraries/avatars/src/AvatarHashMap.cpp b/libraries/avatars/src/AvatarHashMap.cpp index a8f1448a3f..8b7bcc5b20 100644 --- a/libraries/avatars/src/AvatarHashMap.cpp +++ b/libraries/avatars/src/AvatarHashMap.cpp @@ -23,6 +23,8 @@ #include "Profile.h" +static const QString VERIFY_FAIL_MODEL { "/meshes/verifyFailed.fst" }; + void AvatarReplicas::addReplica(const QUuid& parentID, AvatarSharedPointer replica) { if (parentID == QUuid()) { return; @@ -324,6 +326,9 @@ void AvatarHashMap::processAvatarIdentityPacket(QSharedPointer bool displayNameChanged = false; // In this case, the "sendingNode" is the Avatar Mixer. avatar->processAvatarIdentity(avatarIdentityStream, identityChanged, displayNameChanged); + if (avatar->isCertifyFailed()) { + avatar->setSkeletonModelURL(PathUtils::resourcesUrl(VERIFY_FAIL_MODEL)); + } _replicas.processAvatarIdentity(identityUUID, message->getMessage(), identityChanged, displayNameChanged); } } diff --git a/scripts/system/clickToAvatarApp.js b/scripts/system/clickToAvatarApp.js new file mode 100644 index 0000000000..8024f595b5 --- /dev/null +++ b/scripts/system/clickToAvatarApp.js @@ -0,0 +1,7 @@ +(function () { + var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); + this.clickDownOnEntity = function (entityID, mouseEvent) { + tablet.loadQMLSource("hifi/AvatarApp.qml"); + }; +} +);