Fixes to banner; swap out avatar on client; reviewer comments; other WIP

This commit is contained in:
Simon Walton 2019-04-26 18:45:48 -07:00
parent f3efee56c0
commit 1fe8f4332f
12 changed files with 249 additions and 410 deletions

View file

@ -9,6 +9,8 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "MixerAvatar.h"
#include <QRegularExpression>
#include <QJsonObject>
#include <QJsonArray>
@ -22,11 +24,13 @@
#include <NetworkingConstants.h>
#include <EntityItem.h>
#include <EntityItemProperties.h>
#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;
}
}

View file

@ -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<bool> _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();

Binary file not shown.

View file

@ -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

View file

@ -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: "<a href='https://fake.link'>Click here to change your avatar and dismiss this banner.</a>"
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: "<a href='mailto:support@highfidelity.com'>Click here to change your avatar and dismiss this banner.</a>"
onLinkActivated: {
HiFiAbout.openUrl("mailto:support@highfidelity.com");
}
}
}

View file

@ -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: "<a href='https://fake.link'>Click here to change your avatar and dismiss this banner.</a>"
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: "<a href='mailto:support@highfidelity.com'>support@highfidelity.com.</a>"
}
}

View file

@ -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",
});
}
}
}

View file

@ -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();
}

View file

@ -9,45 +9,49 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include <QtGui/QDesktopServices>
#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 <QtGui/QDesktopServices>
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<EntityTreeRenderer>();
EntityTreePointer entityTree = entityTreeRenderer->getTree();
if (!entityTree) {
return;
}
glm::vec3 position { 0.0f, 0.0f, -0.7f };
const bool tabletShown = DependencyManager::get<TabletScriptingInterface>()->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;

View file

@ -12,13 +12,18 @@
#ifndef hifi_AvatarCertifyBanner_h
#define hifi_AvatarCertifyBanner_h
#include <QUuid>
#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:

View file

@ -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<ReceivedMessage>
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);
}
}

View file

@ -0,0 +1,7 @@
(function () {
var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
this.clickDownOnEntity = function (entityID, mouseEvent) {
tablet.loadQMLSource("hifi/AvatarApp.qml");
};
}
);