mirror of
https://github.com/lubosz/overte.git
synced 2025-04-07 01:02:12 +02:00
Fixes to banner; swap out avatar on client; reviewer comments; other WIP
This commit is contained in:
parent
f3efee56c0
commit
1fe8f4332f
12 changed files with 249 additions and 410 deletions
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
BIN
interface/resources/meshes/mannequin/man_stolen.fbx
Normal file
BIN
interface/resources/meshes/mannequin/man_stolen.fbx
Normal file
Binary file not shown.
86
interface/resources/meshes/verifyFailed.fst
Normal file
86
interface/resources/meshes/verifyFailed.fst
Normal 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
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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>"
|
||||
}
|
||||
}
|
|
@ -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",
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
7
scripts/system/clickToAvatarApp.js
Normal file
7
scripts/system/clickToAvatarApp.js
Normal 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");
|
||||
};
|
||||
}
|
||||
);
|
Loading…
Reference in a new issue