mirror of
https://github.com/overte-org/overte.git
synced 2025-08-12 12:34:11 +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
|
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||||
//
|
//
|
||||||
|
|
||||||
|
#include "MixerAvatar.h"
|
||||||
|
|
||||||
#include <QRegularExpression>
|
#include <QRegularExpression>
|
||||||
#include <QJsonObject>
|
#include <QJsonObject>
|
||||||
#include <QJsonArray>
|
#include <QJsonArray>
|
||||||
|
@ -22,11 +24,13 @@
|
||||||
#include <NetworkingConstants.h>
|
#include <NetworkingConstants.h>
|
||||||
#include <EntityItem.h>
|
#include <EntityItem.h>
|
||||||
#include <EntityItemProperties.h>
|
#include <EntityItemProperties.h>
|
||||||
#include "MixerAvatar.h"
|
#include "ClientTraitsHandler.h"
|
||||||
#include "AvatarLogging.h"
|
#include "AvatarLogging.h"
|
||||||
|
|
||||||
|
const QString MixerAvatar::VERIFY_FAIL_MODEL { "qrc:/meshes/verifyFailed.fst" };
|
||||||
|
|
||||||
void MixerAvatar::fetchAvatarFST() {
|
void MixerAvatar::fetchAvatarFST() {
|
||||||
_verifyState = kNoncertified;
|
_verifyState = nonCertified;
|
||||||
_certificateIdFromURL.clear();
|
_certificateIdFromURL.clear();
|
||||||
_certificateIdFromFST.clear();
|
_certificateIdFromFST.clear();
|
||||||
_marketplaceIdFromURL.clear();
|
_marketplaceIdFromURL.clear();
|
||||||
|
@ -60,11 +64,11 @@ void MixerAvatar::fetchAvatarFST() {
|
||||||
}
|
}
|
||||||
_avatarRequest = fstRequest;
|
_avatarRequest = fstRequest;
|
||||||
connect(fstRequest, &ResourceRequest::finished, this, &MixerAvatar::fstRequestComplete);
|
connect(fstRequest, &ResourceRequest::finished, this, &MixerAvatar::fstRequestComplete);
|
||||||
_verifyState = kRequestingFST;
|
_verifyState = requestingFST;
|
||||||
fstRequest->send();
|
fstRequest->send();
|
||||||
} else {
|
} else {
|
||||||
qCDebug(avatars) << "Couldn't create FST request for" << avatarURL;
|
qCDebug(avatars) << "Couldn't create FST request for" << avatarURL;
|
||||||
_verifyState = kError;
|
_verifyState = error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -74,11 +78,11 @@ void MixerAvatar::fstRequestComplete() {
|
||||||
if (fstRequest == _avatarRequest) {
|
if (fstRequest == _avatarRequest) {
|
||||||
auto result = fstRequest->getResult();
|
auto result = fstRequest->getResult();
|
||||||
if (result != ResourceRequest::Success) {
|
if (result != ResourceRequest::Success) {
|
||||||
_verifyState = kError;
|
_verifyState = error;
|
||||||
qCDebug(avatars) << "FST request for" << fstRequest->getUrl() << "failed:" << result;
|
qCDebug(avatars) << "FST request for" << fstRequest->getUrl() << "failed:" << result;
|
||||||
} else {
|
} else {
|
||||||
_avatarFSTContents = fstRequest->getData();
|
_avatarFSTContents = fstRequest->getData();
|
||||||
_verifyState = kReceivedFST;
|
_verifyState = receivedFST;
|
||||||
_pendingEvent = true;
|
_pendingEvent = true;
|
||||||
}
|
}
|
||||||
_avatarRequest->deleteLater();
|
_avatarRequest->deleteLater();
|
||||||
|
@ -101,8 +105,8 @@ bool MixerAvatar::generateFSTHash() {
|
||||||
|
|
||||||
bool MixerAvatar::validateFSTHash(const QString& publicKey) {
|
bool MixerAvatar::validateFSTHash(const QString& publicKey) {
|
||||||
// Guess we should refactor this stuff into a Authorization namespace ...
|
// Guess we should refactor this stuff into a Authorization namespace ...
|
||||||
return EntityItemProperties::verifySignature(publicKey, _certificateHash,
|
return EntityItemProperties::verifySignature(publicKey, _certificateHash,
|
||||||
QByteArray::fromBase64(_certificateIdFromFST.toUtf8()));
|
QByteArray::fromBase64(_certificateIdFromFST.toUtf8()));
|
||||||
}
|
}
|
||||||
|
|
||||||
QByteArray MixerAvatar::canonicalJson(const QString fstFile) {
|
QByteArray MixerAvatar::canonicalJson(const QString fstFile) {
|
||||||
|
@ -164,14 +168,14 @@ void MixerAvatar::ownerRequestComplete() {
|
||||||
|
|
||||||
if (networkReply->error() == QNetworkReply::NoError) {
|
if (networkReply->error() == QNetworkReply::NoError) {
|
||||||
_dynamicMarketResponse = networkReply->readAll();
|
_dynamicMarketResponse = networkReply->readAll();
|
||||||
_verifyState = kOwnerResponse;
|
_verifyState = ownerResponse;
|
||||||
_pendingEvent = true;
|
_pendingEvent = true;
|
||||||
} else {
|
} else {
|
||||||
auto jsonData = QJsonDocument::fromJson(networkReply->readAll())["data"];
|
auto jsonData = QJsonDocument::fromJson(networkReply->readAll())["data"];
|
||||||
if (!jsonData.isUndefined() && !jsonData.toObject()["message"].isUndefined()) {
|
if (!jsonData.isUndefined() && !jsonData.toObject()["message"].isUndefined()) {
|
||||||
qCDebug(avatars) << "Owner lookup failed for" << getDisplayName() << ":"
|
qCDebug(avatars) << "Owner lookup failed for" << getDisplayName() << ":"
|
||||||
<< jsonData.toObject()["message"].toString();
|
<< jsonData.toObject()["message"].toString();
|
||||||
_verifyState = kError;
|
_verifyState = error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
networkReply->deleteLater();
|
networkReply->deleteLater();
|
||||||
|
@ -185,115 +189,116 @@ void MixerAvatar::processCertifyEvents() {
|
||||||
QMutexLocker certifyLocker(&_avatarCertifyLock);
|
QMutexLocker certifyLocker(&_avatarCertifyLock);
|
||||||
switch (_verifyState) {
|
switch (_verifyState) {
|
||||||
|
|
||||||
case kReceivedFST:
|
case receivedFST:
|
||||||
{
|
{
|
||||||
generateFSTHash();
|
generateFSTHash();
|
||||||
QString& marketplacePublicKey = EntityItem::_marketplacePublicKey;
|
QString& marketplacePublicKey = EntityItem::_marketplacePublicKey;
|
||||||
bool staticVerification = validateFSTHash(marketplacePublicKey);
|
bool staticVerification = validateFSTHash(marketplacePublicKey);
|
||||||
_verifyState = staticVerification ? kStaticValidation : kVerificationFailed;
|
_verifyState = staticVerification ? staticValidation : verificationFailed;
|
||||||
|
|
||||||
if (_verifyState == kStaticValidation) {
|
if (_verifyState == staticValidation) {
|
||||||
static const QString POP_MARKETPLACE_API { "/api/v1/commerce/proof_of_purchase_status/transfer" };
|
static const QString POP_MARKETPLACE_API { "/api/v1/commerce/proof_of_purchase_status/transfer" };
|
||||||
auto& networkAccessManager = NetworkAccessManager::getInstance();
|
auto& networkAccessManager = NetworkAccessManager::getInstance();
|
||||||
QNetworkRequest networkRequest;
|
QNetworkRequest networkRequest;
|
||||||
networkRequest.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true);
|
networkRequest.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true);
|
||||||
networkRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
networkRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||||
QUrl requestURL = NetworkingConstants::METAVERSE_SERVER_URL();
|
QUrl requestURL = NetworkingConstants::METAVERSE_SERVER_URL();
|
||||||
requestURL.setPath(POP_MARKETPLACE_API);
|
requestURL.setPath(POP_MARKETPLACE_API);
|
||||||
networkRequest.setUrl(requestURL);
|
networkRequest.setUrl(requestURL);
|
||||||
|
|
||||||
QJsonObject request;
|
QJsonObject request;
|
||||||
request["certificate_id"] = _certificateIdFromFST;
|
request["certificate_id"] = _certificateIdFromFST;
|
||||||
_verifyState = kRequestingOwner;
|
_verifyState = requestingOwner;
|
||||||
QNetworkReply* networkReply = networkAccessManager.put(networkRequest, QJsonDocument(request).toJson());
|
QNetworkReply* networkReply = networkAccessManager.put(networkRequest, QJsonDocument(request).toJson());
|
||||||
connect(networkReply, &QNetworkReply::finished, this, &MixerAvatar::ownerRequestComplete);
|
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;
|
|
||||||
} else {
|
} 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;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
int avatarIDLength;
|
case ownerResponse:
|
||||||
int signedNonceLength;
|
|
||||||
{
|
{
|
||||||
QDataStream responseStream(_challengeResponse);
|
QJsonDocument responseJson = QJsonDocument::fromJson(_dynamicMarketResponse.toUtf8());
|
||||||
responseStream.setByteOrder(QDataStream::LittleEndian);
|
QString ownerPublicKey;
|
||||||
responseStream >> avatarIDLength >> signedNonceLength;
|
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,
|
case challengeResponse:
|
||||||
QByteArray::fromBase64(signedNonce));
|
{
|
||||||
_verifyState = challengeResult ? kVerificationSucceeded : kVerificationFailed;
|
if (_challengeResponse.length() < 8) {
|
||||||
_needsIdentityUpdate = true;
|
_verifyState = error;
|
||||||
if (_verifyState == kVerificationFailed) {
|
break;
|
||||||
qCDebug(avatars) << "Dynamic verification FAILED for " << getDisplayName() << getSessionUUID();
|
}
|
||||||
} else {
|
|
||||||
qCDebug(avatars) << "Dynamic verification SUCCEEDED for " << getDisplayName() << getSessionUUID();
|
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:
|
case requestingOwner:
|
||||||
{ // Qt networking done on this thread:
|
{ // Qt networking done on this thread:
|
||||||
QCoreApplication::processEvents();
|
QCoreApplication::processEvents();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
qCDebug(avatars) << "Unexpected verify state" << _verifyState;
|
qCDebug(avatars) << "Unexpected verify state" << _verifyState;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
} // close switch
|
} // close switch
|
||||||
}
|
}
|
||||||
|
@ -318,19 +323,20 @@ void MixerAvatar::sendOwnerChallenge() {
|
||||||
static constexpr int CHALLENGE_TIMEOUT_MS = 10 * 1000; // 10 s
|
static constexpr int CHALLENGE_TIMEOUT_MS = 10 * 1000; // 10 s
|
||||||
_challengeTimeout.setInterval(CHALLENGE_TIMEOUT_MS);
|
_challengeTimeout.setInterval(CHALLENGE_TIMEOUT_MS);
|
||||||
_challengeTimeout.connect(&_challengeTimeout, &QTimer::timeout, [this]() {
|
_challengeTimeout.connect(&_challengeTimeout, &QTimer::timeout, [this]() {
|
||||||
_verifyState = kVerificationFailed;
|
_verifyState = verificationFailed;
|
||||||
|
setSkeletonModelURL(QUrl(VERIFY_FAIL_MODEL));
|
||||||
_needsIdentityUpdate = true;
|
_needsIdentityUpdate = true;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void MixerAvatar::handleChallengeResponse(ReceivedMessage * response) {
|
void MixerAvatar::handleChallengeResponse(ReceivedMessage* response) {
|
||||||
QByteArray avatarID;
|
QByteArray avatarID;
|
||||||
QByteArray encryptedNonce;
|
QByteArray encryptedNonce;
|
||||||
QMutexLocker certifyLocker(&_avatarCertifyLock);
|
QMutexLocker certifyLocker(&_avatarCertifyLock);
|
||||||
if (_verifyState == kChallengeClient) {
|
if (_verifyState == challengeClient) {
|
||||||
_challengeTimeout.stop();
|
_challengeTimeout.stop();
|
||||||
_challengeResponse = response->readAll();
|
_challengeResponse = response->readAll();
|
||||||
_verifyState = kChallengeResponse;
|
_verifyState = challengeResponse;
|
||||||
_pendingEvent = true;
|
_pendingEvent = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,21 +25,21 @@ public:
|
||||||
void setNeedsHeroCheck(bool needsHeroCheck = true) { _needsHeroCheck = needsHeroCheck; }
|
void setNeedsHeroCheck(bool needsHeroCheck = true) { _needsHeroCheck = needsHeroCheck; }
|
||||||
|
|
||||||
void fetchAvatarFST();
|
void fetchAvatarFST();
|
||||||
virtual bool isCertifyFailed() const override { return _verifyState == kVerificationFailed; }
|
virtual bool isCertifyFailed() const override { return _verifyState == verificationFailed; }
|
||||||
bool needsIdentityUpdate() const { return _needsIdentityUpdate; }
|
bool needsIdentityUpdate() const { return _needsIdentityUpdate; }
|
||||||
void clearIdentityUpdate() { _needsIdentityUpdate = false; }
|
void clearIdentityUpdate() { _needsIdentityUpdate = false; }
|
||||||
|
|
||||||
void processCertifyEvents();
|
void processCertifyEvents();
|
||||||
void handleChallengeResponse(ReceivedMessage * response);
|
void handleChallengeResponse(ReceivedMessage* response);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool _needsHeroCheck { false };
|
bool _needsHeroCheck { false };
|
||||||
|
|
||||||
// Avatar certification/verification:
|
// Avatar certification/verification:
|
||||||
enum VerifyState { kNoncertified, kRequestingFST, kReceivedFST, kStaticValidation, kRequestingOwner, kOwnerResponse,
|
enum VerifyState { nonCertified, requestingFST, receivedFST, staticValidation, requestingOwner, ownerResponse,
|
||||||
kChallengeClient, kChallengeResponse, kVerified, kVerificationFailed, kVerificationSucceeded, kError };
|
challengeClient, challengeResponse, verified, verificationFailed, verificationSucceeded, error };
|
||||||
Q_ENUM(VerifyState);
|
Q_ENUM(VerifyState);
|
||||||
VerifyState _verifyState { kNoncertified };
|
VerifyState _verifyState { nonCertified };
|
||||||
std::atomic<bool> _pendingEvent { false };
|
std::atomic<bool> _pendingEvent { false };
|
||||||
QMutex _avatarCertifyLock;
|
QMutex _avatarCertifyLock;
|
||||||
ResourceRequest* _avatarRequest { nullptr };
|
ResourceRequest* _avatarRequest { nullptr };
|
||||||
|
@ -61,6 +61,8 @@ private:
|
||||||
QByteArray canonicalJson(const QString fstFile);
|
QByteArray canonicalJson(const QString fstFile);
|
||||||
void sendOwnerChallenge();
|
void sendOwnerChallenge();
|
||||||
|
|
||||||
|
static const QString VERIFY_FAIL_MODEL;
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void fstRequestComplete();
|
void fstRequestComplete();
|
||||||
void ownerRequestComplete();
|
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;
|
static AvatarCertifyBanner theftBanner;
|
||||||
if (_myAvatar->isCertifyFailed()) {
|
if (_myAvatar->isCertifyFailed()) {
|
||||||
theftBanner.show(_myAvatar->getSessionUUID(), _myAvatar->getJointIndex("_CAMERA_MATRIX"));
|
theftBanner.show(_myAvatar->getSessionUUID());
|
||||||
} else {
|
} else {
|
||||||
theftBanner.clear();
|
theftBanner.clear();
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,45 +9,49 @@
|
||||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
// 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"
|
#include "AvatarCertifyBanner.h"
|
||||||
|
|
||||||
static const QUrl AVATAR_THEFT_DIALOG = PathUtils::qmlUrl("AvatarTheftSettings.qml");
|
#include <QtGui/QDesktopServices>
|
||||||
static const QUrl AVATAR_THEFT_BANNER_IMAGE = PathUtils::resourcesUrl("images/AvatarTheftBanner.png");
|
|
||||||
|
|
||||||
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) {
|
if (!_active) {
|
||||||
auto entityTreeRenderer = DependencyManager::get<EntityTreeRenderer>();
|
auto entityTreeRenderer = DependencyManager::get<EntityTreeRenderer>();
|
||||||
EntityTreePointer entityTree = entityTreeRenderer->getTree();
|
EntityTreePointer entityTree = entityTreeRenderer->getTree();
|
||||||
if (!entityTree) {
|
if (!entityTree) {
|
||||||
return;
|
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;
|
EntityItemProperties entityProperties;
|
||||||
entityProperties.setType(EntityTypes::Image);
|
entityProperties.setType(EntityTypes::Image);
|
||||||
entityProperties.setImageURL(AVATAR_THEFT_BANNER_IMAGE.toString());
|
entityProperties.setImageURL(AVATAR_THEFT_BANNER_IMAGE.toString());
|
||||||
entityProperties.setName("hifi-avatar-theft-banner");
|
entityProperties.setName("hifi-avatar-notification-banner");
|
||||||
entityProperties.setParentID(avatarID);
|
entityProperties.setParentID(avatarID);
|
||||||
entityProperties.setParentJointIndex(CAMERA_MATRIX_INDEX);
|
entityProperties.setParentJointIndex(CAMERA_MATRIX_INDEX);
|
||||||
entityProperties.setLocalPosition(position);
|
entityProperties.setLocalPosition(position);
|
||||||
entityProperties.setDimensions({ 1.0f, 1.0f, 0.3f });
|
entityProperties.setDimensions(glm::vec3(1.0f, 1.0f, 0.3f) * scaleFactor);
|
||||||
entityProperties.setRenderLayer(RenderLayer::WORLD);
|
entityProperties.setRenderLayer(tabletShown ? RenderLayer::WORLD : RenderLayer::FRONT);
|
||||||
entityProperties.getGrab().setGrabbable(false);
|
entityProperties.getGrab().setGrabbable(false);
|
||||||
|
QString scriptPath = QUrl(PathUtils::defaultScriptsLocation("")).toString() + AVATAR_THEFT_BANNER_SCRIPT;
|
||||||
|
entityProperties.setScript(scriptPath);
|
||||||
entityProperties.setVisible(true);
|
entityProperties.setVisible(true);
|
||||||
|
|
||||||
entityTree->withWriteLock([&] {
|
entityTree->withWriteLock([&] {
|
||||||
auto entityTreeItem = entityTree->addEntity(_bannerID, entityProperties);
|
auto entityTreeItem = entityTree->addEntity(_bannerID, entityProperties);
|
||||||
entityTreeItem->setLocalPosition(position); // ?!
|
entityTreeItem->setLocalPosition(position);
|
||||||
});
|
});
|
||||||
|
|
||||||
_active = true;
|
_active = true;
|
||||||
|
|
|
@ -12,13 +12,18 @@
|
||||||
#ifndef hifi_AvatarCertifyBanner_h
|
#ifndef hifi_AvatarCertifyBanner_h
|
||||||
#define hifi_AvatarCertifyBanner_h
|
#define hifi_AvatarCertifyBanner_h
|
||||||
|
|
||||||
|
#include <QUuid>
|
||||||
|
#include "OffscreenQmlElement.h"
|
||||||
|
#include "EntityItemID.h"
|
||||||
|
|
||||||
|
class EntityItemID;
|
||||||
|
|
||||||
class AvatarCertifyBanner : QObject {
|
class AvatarCertifyBanner : QObject {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
HIFI_QML_DECL
|
HIFI_QML_DECL
|
||||||
public:
|
public:
|
||||||
AvatarCertifyBanner(QQuickItem* parent = nullptr);
|
AvatarCertifyBanner(QQuickItem* parent = nullptr);
|
||||||
~AvatarCertifyBanner();
|
void show(const QUuid& avatarID);
|
||||||
void show(const QUuid& avatarID, int jointIndex);
|
|
||||||
void clear();
|
void clear();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
|
@ -23,6 +23,8 @@
|
||||||
|
|
||||||
#include "Profile.h"
|
#include "Profile.h"
|
||||||
|
|
||||||
|
static const QString VERIFY_FAIL_MODEL { "/meshes/verifyFailed.fst" };
|
||||||
|
|
||||||
void AvatarReplicas::addReplica(const QUuid& parentID, AvatarSharedPointer replica) {
|
void AvatarReplicas::addReplica(const QUuid& parentID, AvatarSharedPointer replica) {
|
||||||
if (parentID == QUuid()) {
|
if (parentID == QUuid()) {
|
||||||
return;
|
return;
|
||||||
|
@ -324,6 +326,9 @@ void AvatarHashMap::processAvatarIdentityPacket(QSharedPointer<ReceivedMessage>
|
||||||
bool displayNameChanged = false;
|
bool displayNameChanged = false;
|
||||||
// In this case, the "sendingNode" is the Avatar Mixer.
|
// In this case, the "sendingNode" is the Avatar Mixer.
|
||||||
avatar->processAvatarIdentity(avatarIdentityStream, identityChanged, displayNameChanged);
|
avatar->processAvatarIdentity(avatarIdentityStream, identityChanged, displayNameChanged);
|
||||||
|
if (avatar->isCertifyFailed()) {
|
||||||
|
avatar->setSkeletonModelURL(PathUtils::resourcesUrl(VERIFY_FAIL_MODEL));
|
||||||
|
}
|
||||||
_replicas.processAvatarIdentity(identityUUID, message->getMessage(), identityChanged, displayNameChanged);
|
_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