mirror of
https://github.com/overte-org/overte.git
synced 2025-08-07 03:19:33 +02:00
Merge branch 'master' of https://github.com/highfidelity/hifi into meta
This commit is contained in:
commit
05304c72e0
20 changed files with 483 additions and 82 deletions
|
@ -12,6 +12,8 @@
|
||||||
|
|
||||||
#include "AssetServer.h"
|
#include "AssetServer.h"
|
||||||
|
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
#include <QtCore/QCoreApplication>
|
#include <QtCore/QCoreApplication>
|
||||||
#include <QtCore/QCryptographicHash>
|
#include <QtCore/QCryptographicHash>
|
||||||
#include <QtCore/QDateTime>
|
#include <QtCore/QDateTime>
|
||||||
|
@ -21,6 +23,7 @@
|
||||||
#include <QtCore/QJsonDocument>
|
#include <QtCore/QJsonDocument>
|
||||||
#include <QtCore/QString>
|
#include <QtCore/QString>
|
||||||
|
|
||||||
|
#include <SharedUtil.h>
|
||||||
#include <ServerPathUtils.h>
|
#include <ServerPathUtils.h>
|
||||||
|
|
||||||
#include "NetworkLogging.h"
|
#include "NetworkLogging.h"
|
||||||
|
@ -28,8 +31,44 @@
|
||||||
#include "SendAssetTask.h"
|
#include "SendAssetTask.h"
|
||||||
#include "UploadAssetTask.h"
|
#include "UploadAssetTask.h"
|
||||||
|
|
||||||
|
static const uint8_t MIN_CORES_FOR_MULTICORE = 4;
|
||||||
|
static const uint8_t CPU_AFFINITY_COUNT_HIGH = 2;
|
||||||
|
static const uint8_t CPU_AFFINITY_COUNT_LOW = 1;
|
||||||
|
static const int INTERFACE_RUNNING_CHECK_FREQUENCY_MS = 1000;
|
||||||
|
|
||||||
const QString ASSET_SERVER_LOGGING_TARGET_NAME = "asset-server";
|
const QString ASSET_SERVER_LOGGING_TARGET_NAME = "asset-server";
|
||||||
|
|
||||||
|
bool interfaceRunning() {
|
||||||
|
bool result = false;
|
||||||
|
|
||||||
|
#ifdef Q_OS_WIN
|
||||||
|
QSharedMemory sharedMemory { getInterfaceSharedMemoryName() };
|
||||||
|
result = sharedMemory.attach(QSharedMemory::ReadOnly);
|
||||||
|
if (result) {
|
||||||
|
sharedMemory.detach();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void updateConsumedCores() {
|
||||||
|
static bool wasInterfaceRunning = false;
|
||||||
|
bool isInterfaceRunning = interfaceRunning();
|
||||||
|
// If state is unchanged, return early
|
||||||
|
if (isInterfaceRunning == wasInterfaceRunning) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
wasInterfaceRunning = isInterfaceRunning;
|
||||||
|
auto coreCount = std::thread::hardware_concurrency();
|
||||||
|
if (isInterfaceRunning) {
|
||||||
|
coreCount = coreCount > MIN_CORES_FOR_MULTICORE ? CPU_AFFINITY_COUNT_HIGH : CPU_AFFINITY_COUNT_LOW;
|
||||||
|
}
|
||||||
|
qDebug() << "Setting max consumed cores to " << coreCount;
|
||||||
|
setMaxCores(coreCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
AssetServer::AssetServer(ReceivedMessage& message) :
|
AssetServer::AssetServer(ReceivedMessage& message) :
|
||||||
ThreadedAssignment(message),
|
ThreadedAssignment(message),
|
||||||
_taskPool(this)
|
_taskPool(this)
|
||||||
|
@ -45,6 +84,20 @@ AssetServer::AssetServer(ReceivedMessage& message) :
|
||||||
packetReceiver.registerListener(PacketType::AssetGetInfo, this, "handleAssetGetInfo");
|
packetReceiver.registerListener(PacketType::AssetGetInfo, this, "handleAssetGetInfo");
|
||||||
packetReceiver.registerListener(PacketType::AssetUpload, this, "handleAssetUpload");
|
packetReceiver.registerListener(PacketType::AssetUpload, this, "handleAssetUpload");
|
||||||
packetReceiver.registerListener(PacketType::AssetMappingOperation, this, "handleAssetMappingOperation");
|
packetReceiver.registerListener(PacketType::AssetMappingOperation, this, "handleAssetMappingOperation");
|
||||||
|
|
||||||
|
#ifdef Q_OS_WIN
|
||||||
|
updateConsumedCores();
|
||||||
|
QTimer* timer = new QTimer(this);
|
||||||
|
auto timerConnection = connect(timer, &QTimer::timeout, [] {
|
||||||
|
updateConsumedCores();
|
||||||
|
});
|
||||||
|
connect(qApp, &QCoreApplication::aboutToQuit, [this, timerConnection] {
|
||||||
|
disconnect(timerConnection);
|
||||||
|
});
|
||||||
|
timer->setInterval(INTERFACE_RUNNING_CHECK_FREQUENCY_MS);
|
||||||
|
timer->setTimerType(Qt::CoarseTimer);
|
||||||
|
timer->start();
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void AssetServer::run() {
|
void AssetServer::run() {
|
||||||
|
|
|
@ -262,8 +262,12 @@ void AvatarMixer::broadcastAvatarData() {
|
||||||
// setup a PacketList for the avatarPackets
|
// setup a PacketList for the avatarPackets
|
||||||
auto avatarPacketList = NLPacketList::create(PacketType::BulkAvatarData);
|
auto avatarPacketList = NLPacketList::create(PacketType::BulkAvatarData);
|
||||||
|
|
||||||
if (avatar.getSessionDisplayName().isEmpty() && // We haven't set it yet...
|
if (nodeData->getAvatarSessionDisplayNameMustChange()) {
|
||||||
nodeData->getReceivedIdentity()) { // ... but we have processed identity (with possible displayName).
|
const QString& existingBaseDisplayName = nodeData->getBaseDisplayName();
|
||||||
|
if (--_sessionDisplayNames[existingBaseDisplayName].second <= 0) {
|
||||||
|
_sessionDisplayNames.remove(existingBaseDisplayName);
|
||||||
|
}
|
||||||
|
|
||||||
QString baseName = avatar.getDisplayName().trimmed();
|
QString baseName = avatar.getDisplayName().trimmed();
|
||||||
const QRegularExpression curses{ "fuck|shit|damn|cock|cunt" }; // POC. We may eventually want something much more elaborate (subscription?).
|
const QRegularExpression curses{ "fuck|shit|damn|cock|cunt" }; // POC. We may eventually want something much more elaborate (subscription?).
|
||||||
baseName = baseName.replace(curses, "*"); // Replace rather than remove, so that people have a clue that the person's a jerk.
|
baseName = baseName.replace(curses, "*"); // Replace rather than remove, so that people have a clue that the person's a jerk.
|
||||||
|
@ -276,11 +280,14 @@ void AvatarMixer::broadcastAvatarData() {
|
||||||
QPair<int, int>& soFar = _sessionDisplayNames[baseName]; // Inserts and answers 0, 0 if not already present, which is what we want.
|
QPair<int, int>& soFar = _sessionDisplayNames[baseName]; // Inserts and answers 0, 0 if not already present, which is what we want.
|
||||||
int& highWater = soFar.first;
|
int& highWater = soFar.first;
|
||||||
nodeData->setBaseDisplayName(baseName);
|
nodeData->setBaseDisplayName(baseName);
|
||||||
avatar.setSessionDisplayName((highWater > 0) ? baseName + "_" + QString::number(highWater) : baseName);
|
QString sessionDisplayName = (highWater > 0) ? baseName + "_" + QString::number(highWater) : baseName;
|
||||||
|
avatar.setSessionDisplayName(sessionDisplayName);
|
||||||
highWater++;
|
highWater++;
|
||||||
soFar.second++; // refcount
|
soFar.second++; // refcount
|
||||||
nodeData->flagIdentityChange();
|
nodeData->flagIdentityChange();
|
||||||
sendIdentityPacket(nodeData, node); // Tell new node about its sessionUUID. Others will find out below.
|
nodeData->setAvatarSessionDisplayNameMustChange(false);
|
||||||
|
sendIdentityPacket(nodeData, node); // Tell node whose name changed about its new session display name. Others will find out below.
|
||||||
|
qDebug() << "Giving session display name" << sessionDisplayName << "to node with ID" << node->getUUID();
|
||||||
}
|
}
|
||||||
|
|
||||||
// this is an AGENT we have received head data from
|
// this is an AGENT we have received head data from
|
||||||
|
@ -584,7 +591,7 @@ void AvatarMixer::handleAvatarIdentityPacket(QSharedPointer<ReceivedMessage> mes
|
||||||
if (avatar.processAvatarIdentity(identity)) {
|
if (avatar.processAvatarIdentity(identity)) {
|
||||||
QMutexLocker nodeDataLocker(&nodeData->getMutex());
|
QMutexLocker nodeDataLocker(&nodeData->getMutex());
|
||||||
nodeData->flagIdentityChange();
|
nodeData->flagIdentityChange();
|
||||||
nodeData->setReceivedIdentity();
|
nodeData->setAvatarSessionDisplayNameMustChange(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -53,8 +53,8 @@ public:
|
||||||
|
|
||||||
HRCTime getIdentityChangeTimestamp() const { return _identityChangeTimestamp; }
|
HRCTime getIdentityChangeTimestamp() const { return _identityChangeTimestamp; }
|
||||||
void flagIdentityChange() { _identityChangeTimestamp = p_high_resolution_clock::now(); }
|
void flagIdentityChange() { _identityChangeTimestamp = p_high_resolution_clock::now(); }
|
||||||
bool getReceivedIdentity() const { return _gotIdentity; }
|
bool getAvatarSessionDisplayNameMustChange() const { return _avatarSessionDisplayNameMustChange; }
|
||||||
void setReceivedIdentity() { _gotIdentity = true; }
|
void setAvatarSessionDisplayNameMustChange(bool set = true) { _avatarSessionDisplayNameMustChange = set; }
|
||||||
|
|
||||||
void setFullRateDistance(float fullRateDistance) { _fullRateDistance = fullRateDistance; }
|
void setFullRateDistance(float fullRateDistance) { _fullRateDistance = fullRateDistance; }
|
||||||
float getFullRateDistance() const { return _fullRateDistance; }
|
float getFullRateDistance() const { return _fullRateDistance; }
|
||||||
|
@ -112,7 +112,7 @@ private:
|
||||||
std::unordered_set<QUuid> _hasReceivedFirstPacketsFrom;
|
std::unordered_set<QUuid> _hasReceivedFirstPacketsFrom;
|
||||||
|
|
||||||
HRCTime _identityChangeTimestamp;
|
HRCTime _identityChangeTimestamp;
|
||||||
bool _gotIdentity { false };
|
bool _avatarSessionDisplayNameMustChange{ false };
|
||||||
|
|
||||||
float _fullRateDistance = FLT_MAX;
|
float _fullRateDistance = FLT_MAX;
|
||||||
float _maxAvatarDistance = FLT_MAX;
|
float _maxAvatarDistance = FLT_MAX;
|
||||||
|
|
Binary file not shown.
|
@ -28,7 +28,7 @@ Item {
|
||||||
property string uuid: ""
|
property string uuid: ""
|
||||||
property string displayName: ""
|
property string displayName: ""
|
||||||
property string userName: ""
|
property string userName: ""
|
||||||
property int displayTextHeight: 18
|
property real displayNameTextPixelSize: 18
|
||||||
property int usernameTextHeight: 12
|
property int usernameTextHeight: 12
|
||||||
property real audioLevel: 0.0
|
property real audioLevel: 0.0
|
||||||
property bool isMyCard: false
|
property bool isMyCard: false
|
||||||
|
@ -55,18 +55,112 @@ Item {
|
||||||
width: parent.width - /*avatarImage.width - parent.spacing - */parent.anchors.leftMargin - parent.anchors.rightMargin
|
width: parent.width - /*avatarImage.width - parent.spacing - */parent.anchors.leftMargin - parent.anchors.rightMargin
|
||||||
height: childrenRect.height
|
height: childrenRect.height
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
// DisplayName Text
|
|
||||||
|
// DisplayName field for my card
|
||||||
|
Rectangle {
|
||||||
|
id: myDisplayName
|
||||||
|
visible: isMyCard
|
||||||
|
// Size
|
||||||
|
width: parent.width + 70
|
||||||
|
height: 35
|
||||||
|
// Anchors
|
||||||
|
anchors.top: parent.top
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.leftMargin: -10
|
||||||
|
// Style
|
||||||
|
color: hifi.colors.textFieldLightBackground
|
||||||
|
border.color: hifi.colors.blueHighlight
|
||||||
|
border.width: 0
|
||||||
|
TextInput {
|
||||||
|
id: myDisplayNameText
|
||||||
|
// Properties
|
||||||
|
text: thisNameCard.displayName
|
||||||
|
maximumLength: 256
|
||||||
|
clip: true
|
||||||
|
// Size
|
||||||
|
width: parent.width
|
||||||
|
height: parent.height
|
||||||
|
// Anchors
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.leftMargin: 10
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.rightMargin: editGlyph.width + editGlyph.anchors.rightMargin
|
||||||
|
// Style
|
||||||
|
color: hifi.colors.darkGray
|
||||||
|
FontLoader { id: firaSansSemiBold; source: "../../fonts/FiraSans-SemiBold.ttf"; }
|
||||||
|
font.family: firaSansSemiBold.name
|
||||||
|
font.pixelSize: displayNameTextPixelSize
|
||||||
|
selectionColor: hifi.colors.blueHighlight
|
||||||
|
selectedTextColor: "black"
|
||||||
|
// Text Positioning
|
||||||
|
verticalAlignment: TextInput.AlignVCenter
|
||||||
|
horizontalAlignment: TextInput.AlignLeft
|
||||||
|
// Signals
|
||||||
|
onEditingFinished: {
|
||||||
|
pal.sendToScript({method: 'displayNameUpdate', params: text})
|
||||||
|
cursorPosition = 0
|
||||||
|
focus = false
|
||||||
|
myDisplayName.border.width = 0
|
||||||
|
color = hifi.colors.darkGray
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
acceptedButtons: Qt.LeftButton
|
||||||
|
hoverEnabled: true
|
||||||
|
onClicked: {
|
||||||
|
myDisplayName.border.width = 1
|
||||||
|
myDisplayNameText.focus ? myDisplayNameText.cursorPosition = myDisplayNameText.positionAt(mouseX, mouseY, TextInput.CursorOnCharacter) : myDisplayNameText.selectAll();
|
||||||
|
myDisplayNameText.focus = true
|
||||||
|
myDisplayNameText.color = "black"
|
||||||
|
}
|
||||||
|
onDoubleClicked: {
|
||||||
|
myDisplayNameText.selectAll();
|
||||||
|
myDisplayNameText.focus = true;
|
||||||
|
}
|
||||||
|
onEntered: myDisplayName.color = hifi.colors.lightGrayText
|
||||||
|
onExited: myDisplayName.color = hifi.colors.textFieldLightBackground
|
||||||
|
}
|
||||||
|
// Edit pencil glyph
|
||||||
|
HiFiGlyphs {
|
||||||
|
id: editGlyph
|
||||||
|
text: hifi.glyphs.editPencil
|
||||||
|
// Text Size
|
||||||
|
size: displayNameTextPixelSize*1.5
|
||||||
|
// Anchors
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.rightMargin: 5
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
// Style
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
verticalAlignment: Text.AlignVCenter
|
||||||
|
color: hifi.colors.baseGray
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Spacer for DisplayName for my card
|
||||||
|
Rectangle {
|
||||||
|
id: myDisplayNameSpacer
|
||||||
|
width: myDisplayName.width
|
||||||
|
// Anchors
|
||||||
|
anchors.top: myDisplayName.bottom
|
||||||
|
height: 5
|
||||||
|
visible: isMyCard
|
||||||
|
opacity: 0
|
||||||
|
}
|
||||||
|
// DisplayName Text for others' cards
|
||||||
FiraSansSemiBold {
|
FiraSansSemiBold {
|
||||||
id: displayNameText
|
id: displayNameText
|
||||||
// Properties
|
// Properties
|
||||||
text: thisNameCard.displayName
|
text: thisNameCard.displayName
|
||||||
elide: Text.ElideRight
|
elide: Text.ElideRight
|
||||||
|
visible: !isMyCard
|
||||||
// Size
|
// Size
|
||||||
width: parent.width
|
width: parent.width
|
||||||
// Anchors
|
// Anchors
|
||||||
anchors.top: parent.top
|
anchors.top: parent.top
|
||||||
// Text Size
|
// Text Size
|
||||||
size: thisNameCard.displayTextHeight
|
size: displayNameTextPixelSize
|
||||||
// Text Positioning
|
// Text Positioning
|
||||||
verticalAlignment: Text.AlignVCenter
|
verticalAlignment: Text.AlignVCenter
|
||||||
// Style
|
// Style
|
||||||
|
@ -83,7 +177,7 @@ Item {
|
||||||
// Size
|
// Size
|
||||||
width: parent.width
|
width: parent.width
|
||||||
// Anchors
|
// Anchors
|
||||||
anchors.top: displayNameText.bottom
|
anchors.top: isMyCard ? myDisplayNameSpacer.bottom : displayNameText.bottom
|
||||||
// Text Size
|
// Text Size
|
||||||
size: thisNameCard.usernameTextHeight
|
size: thisNameCard.usernameTextHeight
|
||||||
// Text Positioning
|
// Text Positioning
|
||||||
|
@ -105,7 +199,7 @@ Item {
|
||||||
Rectangle {
|
Rectangle {
|
||||||
id: nameCardVUMeter
|
id: nameCardVUMeter
|
||||||
// Size
|
// Size
|
||||||
width: ((gainSlider.value - gainSlider.minimumValue)/(gainSlider.maximumValue - gainSlider.minimumValue)) * parent.width
|
width: isMyCard ? myDisplayName.width - 20 : ((gainSlider.value - gainSlider.minimumValue)/(gainSlider.maximumValue - gainSlider.minimumValue)) * parent.width
|
||||||
height: 8
|
height: 8
|
||||||
// Anchors
|
// Anchors
|
||||||
anchors.top: spacer.bottom
|
anchors.top: spacer.bottom
|
||||||
|
|
|
@ -35,6 +35,17 @@ Rectangle {
|
||||||
// Keep a local list of per-avatar gainSliderValueDBs. Far faster than fetching this data from the server.
|
// Keep a local list of per-avatar gainSliderValueDBs. Far faster than fetching this data from the server.
|
||||||
// NOTE: if another script modifies the per-avatar gain, this value won't be accurate!
|
// NOTE: if another script modifies the per-avatar gain, this value won't be accurate!
|
||||||
property var gainSliderValueDB: ({});
|
property var gainSliderValueDB: ({});
|
||||||
|
|
||||||
|
// The letterbox used for popup messages
|
||||||
|
LetterboxMessage {
|
||||||
|
id: letterboxMessage
|
||||||
|
z: 999 // Force the popup on top of everything else
|
||||||
|
}
|
||||||
|
function letterbox(message) {
|
||||||
|
letterboxMessage.text = message
|
||||||
|
letterboxMessage.visible = true
|
||||||
|
letterboxMessage.popupRadius = 0
|
||||||
|
}
|
||||||
|
|
||||||
// This is the container for the PAL
|
// This is the container for the PAL
|
||||||
Rectangle {
|
Rectangle {
|
||||||
|
@ -176,8 +187,6 @@ Rectangle {
|
||||||
TableViewColumn {
|
TableViewColumn {
|
||||||
visible: iAmAdmin
|
visible: iAmAdmin
|
||||||
role: "kick"
|
role: "kick"
|
||||||
// The hacky spaces used to center text over the button, since I don't know how to apply a margin
|
|
||||||
// to column header text.
|
|
||||||
title: "BAN"
|
title: "BAN"
|
||||||
width: actionButtonWidth
|
width: actionButtonWidth
|
||||||
movable: false
|
movable: false
|
||||||
|
@ -337,11 +346,6 @@ Rectangle {
|
||||||
visible: iAmAdmin
|
visible: iAmAdmin
|
||||||
color: hifi.colors.lightGrayText
|
color: hifi.colors.lightGrayText
|
||||||
}
|
}
|
||||||
function letterbox(message) {
|
|
||||||
letterboxMessage.text = message;
|
|
||||||
letterboxMessage.visible = true
|
|
||||||
|
|
||||||
}
|
|
||||||
// This Rectangle refers to the [?] popup button next to "NAMES"
|
// This Rectangle refers to the [?] popup button next to "NAMES"
|
||||||
Rectangle {
|
Rectangle {
|
||||||
color: hifi.colors.tableBackgroundLight
|
color: hifi.colors.tableBackgroundLight
|
||||||
|
@ -402,9 +406,6 @@ Rectangle {
|
||||||
onExited: adminHelpText.color = hifi.colors.redHighlight
|
onExited: adminHelpText.color = hifi.colors.redHighlight
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
LetterboxMessage {
|
|
||||||
id: letterboxMessage
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function findSessionIndex(sessionId, optionalData) { // no findIndex in .qml
|
function findSessionIndex(sessionId, optionalData) { // no findIndex in .qml
|
||||||
|
@ -475,8 +476,6 @@ Rectangle {
|
||||||
// Set the userName appropriately
|
// Set the userName appropriately
|
||||||
userModel.setProperty(userIndex, "userName", userName);
|
userModel.setProperty(userIndex, "userName", userName);
|
||||||
userModelData[userIndex].userName = userName; // Defensive programming
|
userModelData[userIndex].userName = userName; // Defensive programming
|
||||||
} else {
|
|
||||||
console.log("updateUsername() called with unknown UUID: ", userId);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
@ -492,8 +491,6 @@ Rectangle {
|
||||||
if (userIndex != -1) {
|
if (userIndex != -1) {
|
||||||
userModel.setProperty(userIndex, "audioLevel", audioLevel);
|
userModel.setProperty(userIndex, "audioLevel", audioLevel);
|
||||||
userModelData[userIndex].audioLevel = audioLevel; // Defensive programming
|
userModelData[userIndex].audioLevel = audioLevel; // Defensive programming
|
||||||
} else {
|
|
||||||
console.log("updateUsername() called with unknown UUID: ", userId);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -502,6 +499,10 @@ Rectangle {
|
||||||
ignored = {};
|
ignored = {};
|
||||||
gainSliderValueDB = {};
|
gainSliderValueDB = {};
|
||||||
break;
|
break;
|
||||||
|
case 'avatarDisconnected':
|
||||||
|
var sessionID = message.params[0];
|
||||||
|
delete ignored[sessionID];
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
console.log('Unrecognized message:', JSON.stringify(message));
|
console.log('Unrecognized message:', JSON.stringify(message));
|
||||||
}
|
}
|
||||||
|
|
|
@ -312,10 +312,11 @@ Item {
|
||||||
readonly property string error: "="
|
readonly property string error: "="
|
||||||
readonly property string settings: "@"
|
readonly property string settings: "@"
|
||||||
readonly property string trash: "{"
|
readonly property string trash: "{"
|
||||||
readonly property string objectGroup: ""
|
readonly property string objectGroup: "\ue000"
|
||||||
readonly property string cm: "}"
|
readonly property string cm: "}"
|
||||||
readonly property string msvg79: "~"
|
readonly property string msvg79: "~"
|
||||||
readonly property string deg: "\\"
|
readonly property string deg: "\\"
|
||||||
readonly property string px: "|"
|
readonly property string px: "|"
|
||||||
|
readonly property string editPencil: "\ue00d"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1238,8 +1238,11 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
|
||||||
|
|
||||||
// Add periodic checks to send user activity data
|
// Add periodic checks to send user activity data
|
||||||
static int CHECK_NEARBY_AVATARS_INTERVAL_MS = 10000;
|
static int CHECK_NEARBY_AVATARS_INTERVAL_MS = 10000;
|
||||||
static int SEND_STATS_INTERVAL_MS = 10000;
|
|
||||||
static int NEARBY_AVATAR_RADIUS_METERS = 10;
|
static int NEARBY_AVATAR_RADIUS_METERS = 10;
|
||||||
|
|
||||||
|
// setup the stats interval depending on if the 1s faster hearbeat was requested
|
||||||
|
static const QString FAST_STATS_ARG = "--fast-heartbeat";
|
||||||
|
static int SEND_STATS_INTERVAL_MS = arguments().indexOf(FAST_STATS_ARG) != -1 ? 1000 : 10000;
|
||||||
|
|
||||||
static glm::vec3 lastAvatarPosition = myAvatar->getPosition();
|
static glm::vec3 lastAvatarPosition = myAvatar->getPosition();
|
||||||
static glm::mat4 lastHMDHeadPose = getHMDSensorPose();
|
static glm::mat4 lastHMDHeadPose = getHMDSensorPose();
|
||||||
|
|
|
@ -260,6 +260,10 @@ void AvatarManager::handleRemovedAvatar(const AvatarSharedPointer& removedAvatar
|
||||||
}
|
}
|
||||||
if (removalReason == KillAvatarReason::TheirAvatarEnteredYourBubble || removalReason == YourAvatarEnteredTheirBubble) {
|
if (removalReason == KillAvatarReason::TheirAvatarEnteredYourBubble || removalReason == YourAvatarEnteredTheirBubble) {
|
||||||
DependencyManager::get<NodeList>()->radiusIgnoreNodeBySessionID(avatar->getSessionUUID(), true);
|
DependencyManager::get<NodeList>()->radiusIgnoreNodeBySessionID(avatar->getSessionUUID(), true);
|
||||||
|
} else if (removalReason == KillAvatarReason::AvatarDisconnected) {
|
||||||
|
// remove from node sets, if present
|
||||||
|
DependencyManager::get<NodeList>()->removeFromIgnoreMuteSets(avatar->getSessionUUID());
|
||||||
|
DependencyManager::get<UsersScriptingInterface>()->avatarDisconnected(avatar->getSessionUUID());
|
||||||
}
|
}
|
||||||
_avatarFades.push_back(removedAvatar);
|
_avatarFades.push_back(removedAvatar);
|
||||||
}
|
}
|
||||||
|
|
|
@ -56,7 +56,7 @@ int main(int argc, const char* argv[]) {
|
||||||
QCoreApplication::setOrganizationDomain(BuildInfo::ORGANIZATION_DOMAIN);
|
QCoreApplication::setOrganizationDomain(BuildInfo::ORGANIZATION_DOMAIN);
|
||||||
QCoreApplication::setApplicationVersion(BuildInfo::VERSION);
|
QCoreApplication::setApplicationVersion(BuildInfo::VERSION);
|
||||||
|
|
||||||
QString applicationName = "High Fidelity Interface - " + qgetenv("USERNAME");
|
const QString& applicationName = getInterfaceSharedMemoryName();
|
||||||
|
|
||||||
bool instanceMightBeRunning = true;
|
bool instanceMightBeRunning = true;
|
||||||
|
|
||||||
|
|
|
@ -1047,7 +1047,7 @@ bool AvatarData::processAvatarIdentity(const Identity& identity) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (identity.displayName != _displayName) {
|
if (identity.displayName != _displayName) {
|
||||||
setDisplayName(identity.displayName);
|
_displayName = identity.displayName;
|
||||||
hasIdentityChanged = true;
|
hasIdentityChanged = true;
|
||||||
}
|
}
|
||||||
maybeUpdateSessionDisplayNameFromTransport(identity.sessionDisplayName);
|
maybeUpdateSessionDisplayNameFromTransport(identity.sessionDisplayName);
|
||||||
|
@ -1094,6 +1094,9 @@ void AvatarData::setSkeletonModelURL(const QUrl& skeletonModelURL) {
|
||||||
|
|
||||||
void AvatarData::setDisplayName(const QString& displayName) {
|
void AvatarData::setDisplayName(const QString& displayName) {
|
||||||
_displayName = displayName;
|
_displayName = displayName;
|
||||||
|
_sessionDisplayName = "";
|
||||||
|
|
||||||
|
sendIdentityPacket();
|
||||||
|
|
||||||
qCDebug(avatars) << "Changing display name for avatar to" << displayName;
|
qCDebug(avatars) << "Changing display name for avatar to" << displayName;
|
||||||
}
|
}
|
||||||
|
|
|
@ -847,6 +847,16 @@ void NodeList::ignoreNodeBySessionID(const QUuid& nodeID, bool ignoreEnabled) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void NodeList::removeFromIgnoreMuteSets(const QUuid& nodeID) {
|
||||||
|
// don't remove yourself, or nobody
|
||||||
|
if (!nodeID.isNull() && _sessionUUID != nodeID) {
|
||||||
|
QWriteLocker ignoredSetLocker{ &_ignoredSetLock };
|
||||||
|
QWriteLocker personalMutedSetLocker{ &_personalMutedSetLock };
|
||||||
|
_ignoredNodeIDs.unsafe_erase(nodeID);
|
||||||
|
_personalMutedNodeIDs.unsafe_erase(nodeID);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bool NodeList::isIgnoringNode(const QUuid& nodeID) const {
|
bool NodeList::isIgnoringNode(const QUuid& nodeID) const {
|
||||||
QReadLocker ignoredSetLocker{ &_ignoredSetLock };
|
QReadLocker ignoredSetLocker{ &_ignoredSetLock };
|
||||||
return _ignoredNodeIDs.find(nodeID) != _ignoredNodeIDs.cend();
|
return _ignoredNodeIDs.find(nodeID) != _ignoredNodeIDs.cend();
|
||||||
|
|
|
@ -90,6 +90,8 @@ public:
|
||||||
bool getRequestsDomainListData() { return _requestsDomainListData; }
|
bool getRequestsDomainListData() { return _requestsDomainListData; }
|
||||||
void setRequestsDomainListData(bool isRequesting);
|
void setRequestsDomainListData(bool isRequesting);
|
||||||
|
|
||||||
|
void removeFromIgnoreMuteSets(const QUuid& nodeID);
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void reset();
|
void reset();
|
||||||
void sendDomainServerCheckIn();
|
void sendDomainServerCheckIn();
|
||||||
|
|
|
@ -211,7 +211,8 @@ enum class AvatarMixerPacketVersion : PacketVersion {
|
||||||
HandControllerJoints,
|
HandControllerJoints,
|
||||||
HasKillAvatarReason,
|
HasKillAvatarReason,
|
||||||
SessionDisplayName,
|
SessionDisplayName,
|
||||||
Unignore
|
Unignore,
|
||||||
|
ImmediateSessionDisplayNameUpdates
|
||||||
};
|
};
|
||||||
|
|
||||||
enum class DomainConnectRequestVersion : PacketVersion {
|
enum class DomainConnectRequestVersion : PacketVersion {
|
||||||
|
|
|
@ -139,6 +139,13 @@ signals:
|
||||||
*/
|
*/
|
||||||
void usernameFromIDReply(const QString& nodeID, const QString& username, const QString& machineFingerprint);
|
void usernameFromIDReply(const QString& nodeID, const QString& username, const QString& machineFingerprint);
|
||||||
|
|
||||||
|
/**jsdoc
|
||||||
|
* Notifies scripts that a user has disconnected from the domain
|
||||||
|
* @function Users.avatar.avatarDisconnected
|
||||||
|
* @param {nodeID} NodeID The session ID of the avatar that has disconnected
|
||||||
|
*/
|
||||||
|
void avatarDisconnected(const QUuid& nodeID);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool getRequestsDomainListData();
|
bool getRequestsDomainListData();
|
||||||
void setRequestsDomainListData(bool requests);
|
void setRequestsDomainListData(bool requests);
|
||||||
|
|
|
@ -18,6 +18,8 @@
|
||||||
#include <cctype>
|
#include <cctype>
|
||||||
#include <time.h>
|
#include <time.h>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
|
#include <thread>
|
||||||
|
#include <set>
|
||||||
|
|
||||||
#include <glm/glm.hpp>
|
#include <glm/glm.hpp>
|
||||||
|
|
||||||
|
@ -1022,4 +1024,54 @@ bool getProcessorInfo(ProcessorInfo& info) {
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const QString& getInterfaceSharedMemoryName() {
|
||||||
|
static const QString applicationName = "High Fidelity Interface - " + qgetenv("USERNAME");
|
||||||
|
return applicationName;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::vector<uint8_t>& getAvailableCores() {
|
||||||
|
static std::vector<uint8_t> availableCores;
|
||||||
|
#ifdef Q_OS_WIN
|
||||||
|
static std::once_flag once;
|
||||||
|
std::call_once(once, [&] {
|
||||||
|
DWORD_PTR defaultProcessAffinity = 0, defaultSystemAffinity = 0;
|
||||||
|
HANDLE process = GetCurrentProcess();
|
||||||
|
GetProcessAffinityMask(process, &defaultProcessAffinity, &defaultSystemAffinity);
|
||||||
|
for (uint64_t i = 0; i < sizeof(DWORD_PTR) * BITS_IN_BYTE; ++i) {
|
||||||
|
DWORD_PTR coreMask = 1;
|
||||||
|
coreMask <<= i;
|
||||||
|
if (0 != (defaultSystemAffinity & coreMask)) {
|
||||||
|
availableCores.push_back(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
#endif
|
||||||
|
return availableCores;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setMaxCores(uint8_t maxCores) {
|
||||||
|
#ifdef Q_OS_WIN
|
||||||
|
HANDLE process = GetCurrentProcess();
|
||||||
|
auto availableCores = getAvailableCores();
|
||||||
|
if (availableCores.size() <= maxCores) {
|
||||||
|
DWORD_PTR currentProcessAffinity = 0, currentSystemAffinity = 0;
|
||||||
|
GetProcessAffinityMask(process, ¤tProcessAffinity, ¤tSystemAffinity);
|
||||||
|
SetProcessAffinityMask(GetCurrentProcess(), currentSystemAffinity);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
DWORD_PTR newProcessAffinity = 0;
|
||||||
|
while (maxCores) {
|
||||||
|
int index = randIntInRange(0, (int)availableCores.size() - 1);
|
||||||
|
DWORD_PTR coreMask = 1;
|
||||||
|
coreMask <<= availableCores[index];
|
||||||
|
newProcessAffinity |= coreMask;
|
||||||
|
availableCores.erase(availableCores.begin() + index);
|
||||||
|
maxCores--;
|
||||||
|
}
|
||||||
|
SetProcessAffinityMask(process, newProcessAffinity);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
|
@ -231,4 +231,8 @@ struct ProcessorInfo {
|
||||||
|
|
||||||
bool getProcessorInfo(ProcessorInfo& info);
|
bool getProcessorInfo(ProcessorInfo& info);
|
||||||
|
|
||||||
|
const QString& getInterfaceSharedMemoryName();
|
||||||
|
|
||||||
|
void setMaxCores(uint8_t maxCores);
|
||||||
|
|
||||||
#endif // hifi_SharedUtil_h
|
#endif // hifi_SharedUtil_h
|
||||||
|
|
19
scripts/developer/tests/ambientSoundTest.js
Normal file
19
scripts/developer/tests/ambientSoundTest.js
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
var WAVE = 'http://cdn.rawgit.com/ambisonictoolkit/atk-sounds/aa31005c/stereo/Aurora_Surgit-Lux_Aeterna.wav';
|
||||||
|
var uuid = Entities.addEntity({
|
||||||
|
type: "Shape",
|
||||||
|
shape: "Icosahedron",
|
||||||
|
dimensions: Vec3.HALF,
|
||||||
|
script: Script.resolvePath('../../tutorials/entity_scripts/ambientSound.js'),
|
||||||
|
position: Vec3.sum(Vec3.multiply(5, Quat.getFront(MyAvatar.orientation)), MyAvatar.position),
|
||||||
|
userData: JSON.stringify({
|
||||||
|
soundURL: WAVE,
|
||||||
|
maxVolume: 0.1,
|
||||||
|
range: 25,
|
||||||
|
disabled: true,
|
||||||
|
grabbableKey: { wantsTrigger: true },
|
||||||
|
}),
|
||||||
|
lifetime: 600,
|
||||||
|
});
|
||||||
|
Script.scriptEnding.connect(function() {
|
||||||
|
Entities.deleteEntity(uuid);
|
||||||
|
});
|
|
@ -237,6 +237,11 @@ pal.fromQml.connect(function (message) { // messages are {method, params}, like
|
||||||
data = message.params;
|
data = message.params;
|
||||||
Users.setAvatarGain(data['sessionId'], data['gain']);
|
Users.setAvatarGain(data['sessionId'], data['gain']);
|
||||||
break;
|
break;
|
||||||
|
case 'displayNameUpdate':
|
||||||
|
if (MyAvatar.displayName != message.params) {
|
||||||
|
MyAvatar.displayName = message.params;
|
||||||
|
}
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
print('Unrecognized message from Pal.qml:', JSON.stringify(message));
|
print('Unrecognized message from Pal.qml:', JSON.stringify(message));
|
||||||
}
|
}
|
||||||
|
@ -493,7 +498,6 @@ function getAudioLevel(id) {
|
||||||
var audioLevel = 0.0;
|
var audioLevel = 0.0;
|
||||||
var data = id ? ExtendedOverlay.get(id) : myData;
|
var data = id ? ExtendedOverlay.get(id) : myData;
|
||||||
if (!data) {
|
if (!data) {
|
||||||
print('no data for', id);
|
|
||||||
return audioLevel;
|
return audioLevel;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -580,7 +584,10 @@ function onClicked() {
|
||||||
}
|
}
|
||||||
pal.setVisible(!pal.visible);
|
pal.setVisible(!pal.visible);
|
||||||
}
|
}
|
||||||
|
function avatarDisconnected(nodeID) {
|
||||||
|
// remove from the pal list
|
||||||
|
pal.sendToQml({method: 'avatarDisconnected', params: [nodeID]});
|
||||||
|
}
|
||||||
//
|
//
|
||||||
// Button state.
|
// Button state.
|
||||||
//
|
//
|
||||||
|
@ -593,6 +600,8 @@ button.clicked.connect(onClicked);
|
||||||
pal.visibleChanged.connect(onVisibleChanged);
|
pal.visibleChanged.connect(onVisibleChanged);
|
||||||
pal.closed.connect(off);
|
pal.closed.connect(off);
|
||||||
Users.usernameFromIDReply.connect(usernameFromIDReply);
|
Users.usernameFromIDReply.connect(usernameFromIDReply);
|
||||||
|
Users.avatarDisconnected.connect(avatarDisconnected);
|
||||||
|
|
||||||
function clearLocalQMLDataAndClosePAL() {
|
function clearLocalQMLDataAndClosePAL() {
|
||||||
pal.sendToQml({ method: 'clearLocalQMLData' });
|
pal.sendToQml({ method: 'clearLocalQMLData' });
|
||||||
if (pal.visible) {
|
if (pal.visible) {
|
||||||
|
@ -615,6 +624,7 @@ Script.scriptEnding.connect(function () {
|
||||||
Window.domainConnectionRefused.disconnect(clearLocalQMLDataAndClosePAL);
|
Window.domainConnectionRefused.disconnect(clearLocalQMLDataAndClosePAL);
|
||||||
Messages.unsubscribe(CHANNEL);
|
Messages.unsubscribe(CHANNEL);
|
||||||
Messages.messageReceived.disconnect(receiveMessage);
|
Messages.messageReceived.disconnect(receiveMessage);
|
||||||
|
Users.avatarDisconnected.disconnect(avatarDisconnected);
|
||||||
off();
|
off();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -1,34 +1,48 @@
|
||||||
// ambientSound.js
|
// ambientSound.js
|
||||||
//
|
//
|
||||||
// This entity script will allow you to create an ambient sound that loops when a person is within a given
|
// This entity script will allow you to create an ambient sound that loops when a person is within a given
|
||||||
// range of this entity. Great way to add one or more ambisonic soundfields to your environment.
|
// range of this entity. Great way to add one or more ambisonic soundfields to your environment.
|
||||||
//
|
//
|
||||||
// In the userData section for the entity, add/edit three values:
|
// In the userData section for the entity, add/edit three values:
|
||||||
// userData.soundURL should be a string giving the URL to the sound file. Defaults to 100 meters if not set.
|
// userData.soundURL should be a string giving the URL to the sound file. Defaults to 100 meters if not set.
|
||||||
// userData.range should be an integer for the max distance away from the entity where the sound will be audible.
|
// userData.range should be an integer for the max distance away from the entity where the sound will be audible.
|
||||||
// userData.volume is the max volume at which the clip should play. Defaults to 1.0 full volume)
|
// userData.maxVolume is the max volume at which the clip should play. Defaults to 1.0 full volume.
|
||||||
|
// userData.disabled is an optionanl boolean flag which can be used to disable the ambient sound. Defaults to false.
|
||||||
|
//
|
||||||
|
// The rotation of the entity is copied to the ambisonic field, so by rotating the entity you will rotate the
|
||||||
|
// direction in-which a certain sound comes from.
|
||||||
//
|
//
|
||||||
// The rotation of the entity is copied to the ambisonic field, so by rotating the entity you will rotate the
|
|
||||||
// direction in-which a certain sound comes from.
|
|
||||||
//
|
|
||||||
// Remember that the entity has to be visible to the user for the sound to play at all, so make sure the entity is
|
// Remember that the entity has to be visible to the user for the sound to play at all, so make sure the entity is
|
||||||
// large enough to be loaded at the range you set, particularly for large ranges.
|
// large enough to be loaded at the range you set, particularly for large ranges.
|
||||||
//
|
//
|
||||||
// Copyright 2016 High Fidelity, Inc.
|
// Copyright 2016 High Fidelity, Inc.
|
||||||
//
|
//
|
||||||
// Distributed under the Apache License, Version 2.0.
|
// Distributed under the Apache License, Version 2.0.
|
||||||
// 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
|
||||||
//
|
//
|
||||||
|
|
||||||
(function(){
|
(function(){
|
||||||
// This sample clip and range will be used if you don't add userData to the entity (see above)
|
// This sample clip and range will be used if you don't add userData to the entity (see above)
|
||||||
var DEFAULT_RANGE = 100;
|
var DEFAULT_RANGE = 100;
|
||||||
var DEFAULT_URL = "http://hifi-content.s3.amazonaws.com/ken/samples/forest_ambiX.wav";
|
var DEFAULT_URL = "http://hifi-content.s3.amazonaws.com/ken/samples/forest_ambiX.wav";
|
||||||
var DEFAULT_VOLUME = 1.0;
|
var DEFAULT_VOLUME = 1.0;
|
||||||
|
|
||||||
|
var DEFAULT_USERDATA = {
|
||||||
|
soundURL: DEFAULT_URL,
|
||||||
|
range: DEFAULT_RANGE,
|
||||||
|
maxVolume: DEFAULT_VOLUME,
|
||||||
|
disabled: true,
|
||||||
|
grabbableKey: { wantsTrigger: true },
|
||||||
|
};
|
||||||
|
|
||||||
var soundURL = "";
|
var soundURL = "";
|
||||||
|
var soundOptions = {
|
||||||
|
loop: true,
|
||||||
|
localOnly: true,
|
||||||
|
};
|
||||||
var range = DEFAULT_RANGE;
|
var range = DEFAULT_RANGE;
|
||||||
var maxVolume = DEFAULT_VOLUME;
|
var maxVolume = DEFAULT_VOLUME;
|
||||||
|
var disabled = false;
|
||||||
var UPDATE_INTERVAL_MSECS = 100;
|
var UPDATE_INTERVAL_MSECS = 100;
|
||||||
var rotation;
|
var rotation;
|
||||||
|
|
||||||
|
@ -39,14 +53,13 @@
|
||||||
var checkTimer = false;
|
var checkTimer = false;
|
||||||
var _this;
|
var _this;
|
||||||
|
|
||||||
var WANT_COLOR_CHANGE = false;
|
|
||||||
var COLOR_OFF = { red: 128, green: 128, blue: 128 };
|
var COLOR_OFF = { red: 128, green: 128, blue: 128 };
|
||||||
var COLOR_ON = { red: 255, green: 0, blue: 0 };
|
var COLOR_ON = { red: 255, green: 0, blue: 0 };
|
||||||
|
|
||||||
var WANT_DEBUG = false;
|
var WANT_DEBUG = false;
|
||||||
function debugPrint(string) {
|
function debugPrint(string) {
|
||||||
if (WANT_DEBUG) {
|
if (WANT_DEBUG) {
|
||||||
print(string);
|
print("ambientSound | " + string);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -55,84 +68,201 @@
|
||||||
var oldSoundURL = soundURL;
|
var oldSoundURL = soundURL;
|
||||||
var props = Entities.getEntityProperties(entity, [ "userData" ]);
|
var props = Entities.getEntityProperties(entity, [ "userData" ]);
|
||||||
if (props.userData) {
|
if (props.userData) {
|
||||||
var data = JSON.parse(props.userData);
|
try {
|
||||||
|
var data = JSON.parse(props.userData);
|
||||||
|
} catch(e) {
|
||||||
|
debugPrint("unable to parse userData JSON string: " + props.userData);
|
||||||
|
this.cleanup();
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (data.soundURL && !(soundURL === data.soundURL)) {
|
if (data.soundURL && !(soundURL === data.soundURL)) {
|
||||||
soundURL = data.soundURL;
|
soundURL = data.soundURL;
|
||||||
debugPrint("Read ambient sound URL: " + soundURL);
|
debugPrint("Read ambient sound URL: " + soundURL);
|
||||||
} else if (!data.soundURL) {
|
|
||||||
soundURL = DEFAULT_URL;
|
|
||||||
}
|
}
|
||||||
if (data.range && !(range === data.range)) {
|
if (data.range && !(range === data.range)) {
|
||||||
range = data.range;
|
range = data.range;
|
||||||
debugPrint("Read ambient sound range: " + range);
|
debugPrint("Read ambient sound range: " + range);
|
||||||
}
|
}
|
||||||
if (data.volume && !(maxVolume === data.volume)) {
|
// Check known aliases for the "volume" setting (which allows for inplace upgrade of existing marketplace entities)
|
||||||
maxVolume = data.volume;
|
data.maxVolume = data.maxVolume || data.soundVolume || data.volume;
|
||||||
|
if (data.maxVolume && !(maxVolume === data.maxVolume)) {
|
||||||
|
maxVolume = data.maxVolume;
|
||||||
debugPrint("Read ambient sound volume: " + maxVolume);
|
debugPrint("Read ambient sound volume: " + maxVolume);
|
||||||
}
|
}
|
||||||
|
if ("disabled" in data && !(disabled === data.disabled)) {
|
||||||
|
disabled = data.disabled;
|
||||||
|
debugPrint("Read ambient disabled state: " + disabled);
|
||||||
|
this._updateColor(disabled);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (disabled) {
|
||||||
|
this.cleanup();
|
||||||
|
soundURL = "";
|
||||||
|
return;
|
||||||
|
} else if (!checkTimer) {
|
||||||
|
checkTimer = Script.setInterval(_this.maybeUpdate, UPDATE_INTERVAL_MSECS);
|
||||||
}
|
}
|
||||||
if (!(soundURL === oldSoundURL) || (soundURL === "")) {
|
if (!(soundURL === oldSoundURL) || (soundURL === "")) {
|
||||||
debugPrint("Loading ambient sound into cache");
|
if (soundURL) {
|
||||||
ambientSound = SoundCache.getSound(soundURL);
|
debugPrint("Loading ambient sound into cache");
|
||||||
|
// Use prefetch to detect URL loading errors
|
||||||
|
var resource = SoundCache.prefetch(soundURL);
|
||||||
|
function onStateChanged() {
|
||||||
|
if (resource.state === Resource.State.FINISHED) {
|
||||||
|
resource.stateChanged.disconnect(onStateChanged);
|
||||||
|
ambientSound = SoundCache.getSound(soundURL);
|
||||||
|
} else if (resource.state === Resource.State.FAILED) {
|
||||||
|
resource.stateChanged.disconnect(onStateChanged);
|
||||||
|
debugPrint("Failed to download ambient sound: " + soundURL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
resource.stateChanged.connect(onStateChanged);
|
||||||
|
onStateChanged(resource.state);
|
||||||
|
}
|
||||||
if (soundPlaying && soundPlaying.playing) {
|
if (soundPlaying && soundPlaying.playing) {
|
||||||
|
debugPrint("URL changed, stopping current ambient sound");
|
||||||
soundPlaying.stop();
|
soundPlaying.stop();
|
||||||
soundPlaying = false;
|
soundPlaying = false;
|
||||||
if (WANT_COLOR_CHANGE) {
|
|
||||||
Entities.editEntity(entity, { color: COLOR_OFF });
|
|
||||||
}
|
|
||||||
debugPrint("Restarting ambient sound");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.preload = function(entityID) {
|
this.clickDownOnEntity = function(entityID, mouseEvent) {
|
||||||
|
if (mouseEvent.isPrimaryButton) {
|
||||||
|
this._toggle("primary click");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
this.startFarTrigger = function() {
|
||||||
|
this._toggle("far click");
|
||||||
|
};
|
||||||
|
|
||||||
|
this._toggle = function(hint) {
|
||||||
|
// Toggle between ON/OFF state, but only if not in edit mode
|
||||||
|
if (Settings.getValue("io.highfidelity.isEditting")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var props = Entities.getEntityProperties(entity, [ "userData" ]);
|
||||||
|
if (!props.userData) {
|
||||||
|
debugPrint("userData is empty; ignoring " + hint);
|
||||||
|
this.cleanup();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var data = JSON.parse(props.userData);
|
||||||
|
data.disabled = !data.disabled;
|
||||||
|
|
||||||
|
debugPrint(hint + " -- triggering ambient sound " + (data.disabled ? "OFF" : "ON") + " (" + data.soundURL + ")");
|
||||||
|
|
||||||
|
this.cleanup();
|
||||||
|
|
||||||
|
// Save the userData and notify nearby listeners of the change
|
||||||
|
Entities.editEntity(entity, {
|
||||||
|
userData: JSON.stringify(data)
|
||||||
|
});
|
||||||
|
Messages.sendMessage(entity, "toggled");
|
||||||
|
};
|
||||||
|
|
||||||
|
this._updateColor = function(disabled) {
|
||||||
|
// Update Shape or Text Entity color based on ON/OFF status
|
||||||
|
var props = Entities.getEntityProperties(entity, [ "color", "textColor" ]);
|
||||||
|
var targetColor = disabled ? COLOR_OFF : COLOR_ON;
|
||||||
|
var currentColor = props.textColor || props.color;
|
||||||
|
var newProps = props.textColor ? { textColor: targetColor } : { color: targetColor };
|
||||||
|
|
||||||
|
if (currentColor.red !== targetColor.red ||
|
||||||
|
currentColor.green !== targetColor.green ||
|
||||||
|
currentColor.blue !== targetColor.blue) {
|
||||||
|
Entities.editEntity(entity, newProps);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
this.preload = function(entityID) {
|
||||||
// Load the sound and range from the entity userData fields, and note the position of the entity.
|
// Load the sound and range from the entity userData fields, and note the position of the entity.
|
||||||
debugPrint("Ambient sound preload");
|
debugPrint("Ambient sound preload " + entityID);
|
||||||
entity = entityID;
|
entity = entityID;
|
||||||
_this = this;
|
_this = this;
|
||||||
checkTimer = Script.setInterval(this.maybeUpdate, UPDATE_INTERVAL_MSECS);
|
|
||||||
};
|
var props = Entities.getEntityProperties(entity, [ "userData" ]);
|
||||||
|
var data = {};
|
||||||
|
if (props.userData) {
|
||||||
|
data = JSON.parse(props.userData);
|
||||||
|
}
|
||||||
|
var changed = false;
|
||||||
|
for(var p in DEFAULT_USERDATA) {
|
||||||
|
if (!(p in data)) {
|
||||||
|
data[p] = DEFAULT_USERDATA[p];
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!data.grabbableKey.wantsTrigger) {
|
||||||
|
data.grabbableKey.wantsTrigger = true;
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
if (changed) {
|
||||||
|
debugPrint("applying default values to userData");
|
||||||
|
Entities.editEntity(entity, { userData: JSON.stringify(data) });
|
||||||
|
}
|
||||||
|
this._updateColor(data.disabled);
|
||||||
|
this.updateSettings();
|
||||||
|
|
||||||
|
// Subscribe to toggle notifications using entity ID as a channel name
|
||||||
|
Messages.subscribe(entity);
|
||||||
|
Messages.messageReceived.connect(this, "_onMessageReceived");
|
||||||
|
};
|
||||||
|
|
||||||
|
this._onMessageReceived = function(channel, message, sender, local) {
|
||||||
|
// Handle incoming toggle notifications
|
||||||
|
if (channel === entity && message === "toggled") {
|
||||||
|
debugPrint("received " + message + " from " + sender);
|
||||||
|
this.updateSettings();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
this.maybeUpdate = function() {
|
this.maybeUpdate = function() {
|
||||||
// Every UPDATE_INTERVAL_MSECS, update the volume of the ambient sound based on distance from my avatar
|
// Every UPDATE_INTERVAL_MSECS, update the volume of the ambient sound based on distance from my avatar
|
||||||
_this.updateSettings();
|
_this.updateSettings();
|
||||||
var HYSTERESIS_FRACTION = 0.1;
|
var HYSTERESIS_FRACTION = 0.1;
|
||||||
var props = Entities.getEntityProperties(entity, [ "position", "rotation" ]);
|
var props = Entities.getEntityProperties(entity, [ "position", "rotation" ]);
|
||||||
|
if (disabled || !props.position) {
|
||||||
|
_this.cleanup();
|
||||||
|
return;
|
||||||
|
}
|
||||||
center = props.position;
|
center = props.position;
|
||||||
rotation = props.rotation;
|
rotation = props.rotation;
|
||||||
var distance = Vec3.length(Vec3.subtract(MyAvatar.position, center));
|
var distance = Vec3.length(Vec3.subtract(MyAvatar.position, center));
|
||||||
if (distance <= range) {
|
if (distance <= range) {
|
||||||
var volume = (1.0 - distance / range) * maxVolume;
|
var volume = (1.0 - distance / range) * maxVolume;
|
||||||
if (!soundPlaying && ambientSound.downloaded) {
|
soundOptions.orientation = rotation;
|
||||||
soundPlaying = Audio.playSound(ambientSound, { loop: true,
|
soundOptions.volume = volume;
|
||||||
localOnly: true,
|
if (!soundPlaying && ambientSound && ambientSound.downloaded) {
|
||||||
orientation: rotation,
|
debugPrint("Starting ambient sound: " + soundURL + " (duration: " + ambientSound.duration + ")");
|
||||||
volume: volume });
|
soundPlaying = Audio.playSound(ambientSound, soundOptions);
|
||||||
debugPrint("Starting ambient sound, volume: " + volume);
|
|
||||||
if (WANT_COLOR_CHANGE) {
|
|
||||||
Entities.editEntity(entity, { color: COLOR_ON });
|
|
||||||
}
|
|
||||||
|
|
||||||
} else if (soundPlaying && soundPlaying.playing) {
|
} else if (soundPlaying && soundPlaying.playing) {
|
||||||
soundPlaying.setOptions( { volume: volume, orientation: rotation } );
|
soundPlaying.setOptions(soundOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
} else if (soundPlaying && soundPlaying.playing && (distance > range * HYSTERESIS_FRACTION)) {
|
} else if (soundPlaying && soundPlaying.playing && (distance > range * HYSTERESIS_FRACTION)) {
|
||||||
soundPlaying.stop();
|
soundPlaying.stop();
|
||||||
soundPlaying = false;
|
soundPlaying = false;
|
||||||
Entities.editEntity(entity, { color: { red: 128, green: 128, blue: 128 }});
|
debugPrint("Out of range, stopping ambient sound: " + soundURL);
|
||||||
debugPrint("Out of range, stopping ambient sound");
|
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
this.unload = function(entityID) {
|
this.unload = function(entityID) {
|
||||||
debugPrint("Ambient sound unload");
|
debugPrint("Ambient sound unload");
|
||||||
|
this.cleanup();
|
||||||
|
Messages.unsubscribe(entity);
|
||||||
|
Messages.messageReceived.disconnect(this, "_onMessageReceived");
|
||||||
|
};
|
||||||
|
|
||||||
|
this.cleanup = function() {
|
||||||
if (checkTimer) {
|
if (checkTimer) {
|
||||||
Script.clearInterval(checkTimer);
|
Script.clearInterval(checkTimer);
|
||||||
|
checkTimer = false;
|
||||||
}
|
}
|
||||||
if (soundPlaying && soundPlaying.playing) {
|
if (soundPlaying && soundPlaying.playing) {
|
||||||
soundPlaying.stop();
|
soundPlaying.stop();
|
||||||
|
soundPlaying = false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
Loading…
Reference in a new issue