"use strict";//// bubble.js// scripts/system///// Created by Brad Hefta-Gaub on November 18th, 2016// Copyright 2016 High Fidelity, Inc.// Copyright 2023 Overte e.V.//// Distributed under the Apache License, Version 2.0.// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html///* global Script, Users, Entities, AvatarList, Controller, Camera, getControllerWorldLocation, UserActivityLogger */ (function () { // BEGIN LOCAL_SCOPE var button; // Used for animating and disappearing the bubble var bubbleOverlayTimestamp; // Used for rate limiting the bubble sound var lastBubbleSoundTimestamp = 0; // Affects bubble height var BUBBLE_HEIGHT_SCALE = 0.15; // The bubble model itself var bubbleOverlay = Entities.addEntity({ "type": "Model", "modelURL": Script.resolvePath("assets/models/Bubble-v14.fbx"), // If you'd like to change the model, modify this line (and the dimensions below) "dimensions": { "x": MyAvatar.sensorToWorldScale, "y": (0.75 * MyAvatar.sensorToWorldScale), "z": MyAvatar.sensorToWorldScale }, "position": { "x": MyAvatar.position.x, "y": -MyAvatar.scale * 2 + MyAvatar.position.y + MyAvatar.scale * BUBBLE_HEIGHT_SCALE, "z": MyAvatar.position.z }, "rotation": Quat.multiply(MyAvatar.orientation, Quat.fromVec3Degrees({"x": 0.0, "y": 180.0, "z": 0.0})), "visible": false, "ignorePickIntersection": true }, "local"); // The bubble activation sound var bubbleActivateSound = SoundCache.getSound(Script.resolvePath("assets/sounds/bubble.wav")); // Is the update() function connected? var updateConnected = false; var BUBBLE_VISIBLE_DURATION_MS = 3000; var BUBBLE_RAISE_ANIMATION_DURATION_MS = 750; var BUBBLE_SOUND_RATE_LIMIT_MS = 15000; // Hides the bubble model overlay function hideOverlays() { Entities.editEntity(bubbleOverlay, { "visible": false }); } //create a menu item in "Setings" to toggle the bubble/shield HUD button var menuItemName = "HUD Shield Button"; Menu.addMenuItem({ menuName: "Settings", menuItemName: menuItemName, isCheckable: true, isChecked: AvatarInputs.showBubbleTools }); Menu.menuItemEvent.connect(onToggleHudShieldButton); AvatarInputs.showBubbleToolsChanged.connect(showBubbleToolsChanged); function onToggleHudShieldButton(menuItem) { if (menuItem === menuItemName) { AvatarInputs.setShowBubbleTools(Menu.isOptionChecked(menuItem)); }; } function showBubbleToolsChanged(show) { Menu.setIsOptionChecked(menuItemName, show); } // Make the bubble overlay visible, set its position, and play the sound function createOverlays() { var nowTimestamp = Date.now(); if (nowTimestamp - lastBubbleSoundTimestamp >= BUBBLE_SOUND_RATE_LIMIT_MS) { Audio.playSound(bubbleActivateSound, { position: { x: MyAvatar.position.x, y: MyAvatar.position.y, z: MyAvatar.position.z }, localOnly: true, volume: 0.2 }); lastBubbleSoundTimestamp = nowTimestamp; } hideOverlays(); if (updateConnected === true) { updateConnected = false; Script.update.disconnect(update); } Entities.editEntity(bubbleOverlay, { "dimensions": { "x": MyAvatar.sensorToWorldScale, "y": 0.75 * MyAvatar.sensorToWorldScale, "z": MyAvatar.sensorToWorldScale }, "position": { "x": MyAvatar.position.x, "y": -MyAvatar.scale * 2 + MyAvatar.position.y + MyAvatar.scale * BUBBLE_HEIGHT_SCALE, "z": MyAvatar.position.z }, "rotation": Quat.multiply(MyAvatar.orientation, Quat.fromVec3Degrees({"x": 0.0, "y": 180.0, "z": 0.0})), "visible": true }); bubbleOverlayTimestamp = nowTimestamp; Script.update.connect(update); updateConnected = true; } // Called from the C++ scripting interface to show the bubble overlay function enteredIgnoreRadius() { createOverlays(); UserActivityLogger.privacyShieldActivated(); } // Used to set the state of the bubble HUD button function writeButtonProperties(parameter) { button.editProperties({isActive: parameter}); } // The bubble script's update function function update() { var timestamp = Date.now(); var delay = (timestamp - bubbleOverlayTimestamp); var overlayAlpha = 1.0 - (delay / BUBBLE_VISIBLE_DURATION_MS); if (overlayAlpha > 0) { if (delay < BUBBLE_RAISE_ANIMATION_DURATION_MS) { Entities.editEntity(bubbleOverlay, { "dimensions": { "x": MyAvatar.sensorToWorldScale, "y": (0.75 * MyAvatar.sensorToWorldScale) * (1 - ((BUBBLE_RAISE_ANIMATION_DURATION_MS - delay) / BUBBLE_RAISE_ANIMATION_DURATION_MS)), "z": MyAvatar.sensorToWorldScale }, // Quickly raise the bubble from the ground up "position": { "x": MyAvatar.position.x, "y": (-((BUBBLE_RAISE_ANIMATION_DURATION_MS - delay) / BUBBLE_RAISE_ANIMATION_DURATION_MS)) * MyAvatar.scale * 2 + MyAvatar.position.y + MyAvatar.scale * BUBBLE_HEIGHT_SCALE, "z": MyAvatar.position.z }, "rotation": Quat.multiply(MyAvatar.orientation, Quat.fromVec3Degrees({"x": 0.0, "y": 180.0, "z": 0.0})) }); } else { // Keep the bubble in place for a couple seconds Entities.editEntity(bubbleOverlay, { "dimensions": { "x": MyAvatar.sensorToWorldScale, "y": 0.75 * MyAvatar.sensorToWorldScale, "z": MyAvatar.sensorToWorldScale }, "position": { "x": MyAvatar.position.x, "y": MyAvatar.position.y + MyAvatar.scale * BUBBLE_HEIGHT_SCALE, "z": MyAvatar.position.z }, "rotation": Quat.multiply(MyAvatar.orientation, Quat.fromVec3Degrees({"x": 0.0, "y": 180.0, "z": 0.0})) }); } } else { hideOverlays(); if (updateConnected === true) { Script.update.disconnect(update); updateConnected = false; } } } // When the space bubble is toggled... // NOTE: the c++ calls this with just the first param -- we added a second // just for not logging the initial state of the bubble when we startup. function onBubbleToggled(enabled, doNotLog) { writeButtonProperties(enabled); if (doNotLog !== true) { UserActivityLogger.privacyShieldToggled(enabled); } if (enabled) { createOverlays(); } else { hideOverlays(); if (updateConnected === true) { Script.update.disconnect(update); updateConnected = false; } } } // Setup the bubble button var buttonName = "SHIELD"; var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); button = tablet.addButton({ icon: "icons/tablet-icons/bubble-i.svg", activeIcon: "icons/tablet-icons/bubble-a.svg", text: buttonName, sortOrder: 4 }); onBubbleToggled(Users.getIgnoreRadiusEnabled(), true); // pass in true so we don't log this initial one in the UserActivity table button.clicked.connect(Users.toggleIgnoreRadius); Users.ignoreRadiusEnabledChanged.connect(onBubbleToggled); Users.enteredIgnoreRadius.connect(enteredIgnoreRadius); // Cleanup the tablet button and overlays when script is stopped Script.scriptEnding.connect(function () { Menu.menuItemEvent.disconnect(onToggleHudShieldButton); AvatarInputs.showBubbleToolsChanged.disconnect(showBubbleToolsChanged); Menu.removeMenuItem("Settings", menuItemName); button.clicked.disconnect(Users.toggleIgnoreRadius); if (tablet) { tablet.removeButton(button); } Users.ignoreRadiusEnabledChanged.disconnect(onBubbleToggled); Users.enteredIgnoreRadius.disconnect(enteredIgnoreRadius); Entities.deleteEntity(bubbleOverlay); if (updateConnected === true) { Script.update.disconnect(update); } }); }()); // END LOCAL_SCOPE
"use strict";
//
// bubble.js
// scripts/system/
//
// Created by Brad Hefta-Gaub on November 18th, 2016
// Copyright 2016 High Fidelity, Inc.
// Copyright 2023 Overte e.V.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
/* global Script, Users, Entities, AvatarList, Controller, Camera, getControllerWorldLocation, UserActivityLogger */
(function () { // BEGIN LOCAL_SCOPE
var button;
// Used for animating and disappearing the bubble
var bubbleOverlayTimestamp;
// Used for rate limiting the bubble sound
var lastBubbleSoundTimestamp = 0;
// Affects bubble height
var BUBBLE_HEIGHT_SCALE = 0.15;
// The bubble model itself
var bubbleOverlay = Entities.addEntity({
"type": "Model",
"modelURL": Script.resolvePath("assets/models/Bubble-v14.fbx"), // If you'd like to change the model, modify this line (and the dimensions below)
"dimensions": {
"x": MyAvatar.sensorToWorldScale,
"y": (0.75 * MyAvatar.sensorToWorldScale),
"z": MyAvatar.sensorToWorldScale
},
"position": { "x": MyAvatar.position.x, "y": -MyAvatar.scale * 2 + MyAvatar.position.y + MyAvatar.scale * BUBBLE_HEIGHT_SCALE, "z": MyAvatar.position.z },
"rotation": Quat.multiply(MyAvatar.orientation, Quat.fromVec3Degrees({"x": 0.0, "y": 180.0, "z": 0.0})),
"visible": false,
"ignorePickIntersection": true
}, "local");
// The bubble activation sound
var bubbleActivateSound = SoundCache.getSound(Script.resolvePath("assets/sounds/bubble.wav"));
// Is the update() function connected?
var updateConnected = false;
var BUBBLE_VISIBLE_DURATION_MS = 3000;
var BUBBLE_RAISE_ANIMATION_DURATION_MS = 750;
var BUBBLE_SOUND_RATE_LIMIT_MS = 15000;
// Hides the bubble model overlay
function hideOverlays() {
Entities.editEntity(bubbleOverlay, {
"visible": false
});
}
//create a menu item in "Setings" to toggle the bubble/shield HUD button
var menuItemName = "HUD Shield Button";
Menu.addMenuItem({
menuName: "Settings",
menuItemName: menuItemName,
isCheckable: true,
isChecked: AvatarInputs.showBubbleTools
});
Menu.menuItemEvent.connect(onToggleHudShieldButton);
AvatarInputs.showBubbleToolsChanged.connect(showBubbleToolsChanged);
function onToggleHudShieldButton(menuItem) {
if (menuItem === menuItemName) {
AvatarInputs.setShowBubbleTools(Menu.isOptionChecked(menuItem));
};
}
function showBubbleToolsChanged(show) {
Menu.setIsOptionChecked(menuItemName, show);
}
// Make the bubble overlay visible, set its position, and play the sound
function createOverlays() {
var nowTimestamp = Date.now();
if (nowTimestamp - lastBubbleSoundTimestamp >= BUBBLE_SOUND_RATE_LIMIT_MS) {
Audio.playSound(bubbleActivateSound, {
position: { x: MyAvatar.position.x, y: MyAvatar.position.y, z: MyAvatar.position.z },
localOnly: true,
volume: 0.2
});
lastBubbleSoundTimestamp = nowTimestamp;
}
hideOverlays();
if (updateConnected === true) {
updateConnected = false;
Script.update.disconnect(update);
}
Entities.editEntity(bubbleOverlay, {
"dimensions": {
"x": MyAvatar.sensorToWorldScale,
"y": 0.75 * MyAvatar.sensorToWorldScale,
"z": MyAvatar.sensorToWorldScale
},
"position": {
"x": MyAvatar.position.x,
"y": -MyAvatar.scale * 2 + MyAvatar.position.y + MyAvatar.scale * BUBBLE_HEIGHT_SCALE,
"z": MyAvatar.position.z
},
"rotation": Quat.multiply(MyAvatar.orientation, Quat.fromVec3Degrees({"x": 0.0, "y": 180.0, "z": 0.0})),
"visible": true
});
bubbleOverlayTimestamp = nowTimestamp;
Script.update.connect(update);
updateConnected = true;
}
// Called from the C++ scripting interface to show the bubble overlay
function enteredIgnoreRadius() {
createOverlays();
UserActivityLogger.privacyShieldActivated();
}
// Used to set the state of the bubble HUD button
function writeButtonProperties(parameter) {
button.editProperties({isActive: parameter});
}
// The bubble script's update function
function update() {
var timestamp = Date.now();
var delay = (timestamp - bubbleOverlayTimestamp);
var overlayAlpha = 1.0 - (delay / BUBBLE_VISIBLE_DURATION_MS);
if (overlayAlpha > 0) {
if (delay < BUBBLE_RAISE_ANIMATION_DURATION_MS) {
Entities.editEntity(bubbleOverlay, {
"dimensions": {
"x": MyAvatar.sensorToWorldScale,
"y": (0.75 * MyAvatar.sensorToWorldScale) * (1 - ((BUBBLE_RAISE_ANIMATION_DURATION_MS - delay) / BUBBLE_RAISE_ANIMATION_DURATION_MS)),
"z": MyAvatar.sensorToWorldScale
},
// Quickly raise the bubble from the ground up
"position": {
"x": MyAvatar.position.x,
"y": (-((BUBBLE_RAISE_ANIMATION_DURATION_MS - delay) / BUBBLE_RAISE_ANIMATION_DURATION_MS)) * MyAvatar.scale * 2 + MyAvatar.position.y + MyAvatar.scale * BUBBLE_HEIGHT_SCALE,
"z": MyAvatar.position.z
},
"rotation": Quat.multiply(MyAvatar.orientation, Quat.fromVec3Degrees({"x": 0.0, "y": 180.0, "z": 0.0}))
});
} else {
// Keep the bubble in place for a couple seconds
Entities.editEntity(bubbleOverlay, {
"dimensions": {
"x": MyAvatar.sensorToWorldScale,
"y": 0.75 * MyAvatar.sensorToWorldScale,
"z": MyAvatar.sensorToWorldScale
},
"position": {
"x": MyAvatar.position.x,
"y": MyAvatar.position.y + MyAvatar.scale * BUBBLE_HEIGHT_SCALE,
"z": MyAvatar.position.z
},
"rotation": Quat.multiply(MyAvatar.orientation, Quat.fromVec3Degrees({"x": 0.0, "y": 180.0, "z": 0.0}))
});
}
} else {
hideOverlays();
if (updateConnected === true) {
Script.update.disconnect(update);
updateConnected = false;
}
}
}
// When the space bubble is toggled...
// NOTE: the c++ calls this with just the first param -- we added a second
// just for not logging the initial state of the bubble when we startup.
function onBubbleToggled(enabled, doNotLog) {
writeButtonProperties(enabled);
if (doNotLog !== true) {
UserActivityLogger.privacyShieldToggled(enabled);
}
if (enabled) {
createOverlays();
} else {
hideOverlays();
if (updateConnected === true) {
Script.update.disconnect(update);
updateConnected = false;
}
}
}
// Setup the bubble button
var buttonName = "SHIELD";
var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
button = tablet.addButton({
icon: "icons/tablet-icons/bubble-i.svg",
activeIcon: "icons/tablet-icons/bubble-a.svg",
text: buttonName,
sortOrder: 4
});
onBubbleToggled(Users.getIgnoreRadiusEnabled(), true); // pass in true so we don't log this initial one in the UserActivity table
button.clicked.connect(Users.toggleIgnoreRadius);
Users.ignoreRadiusEnabledChanged.connect(onBubbleToggled);
Users.enteredIgnoreRadius.connect(enteredIgnoreRadius);
// Cleanup the tablet button and overlays when script is stopped
Script.scriptEnding.connect(function () {
Menu.menuItemEvent.disconnect(onToggleHudShieldButton);
AvatarInputs.showBubbleToolsChanged.disconnect(showBubbleToolsChanged);
Menu.removeMenuItem("Settings", menuItemName);
button.clicked.disconnect(Users.toggleIgnoreRadius);
if (tablet) {
tablet.removeButton(button);
}
Users.ignoreRadiusEnabledChanged.disconnect(onBubbleToggled);
Users.enteredIgnoreRadius.disconnect(enteredIgnoreRadius);
Entities.deleteEntity(bubbleOverlay);
if (updateConnected === true) {
Script.update.disconnect(update);
}
});
}()); // END LOCAL_SCOPE