296 lines
12 KiB
JavaScript
296 lines
12 KiB
JavaScript
//
|
|
// question_app.js
|
|
//
|
|
// Created by Rebecca Stankus on 02/21/19
|
|
// Copyright 2019 High Fidelity, Inc.
|
|
//
|
|
// Distributed under the Apache License, Version 2.0.
|
|
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
|
//
|
|
|
|
/* global Audio, Camera, Controller, Entities, HMD, Messages, MyAvatar, Quat, Script, Settings, SoundCache,
|
|
Tablet, Users, Vec3, Window */
|
|
|
|
(function() {
|
|
|
|
// *************************************
|
|
// START UTILITY FUNCTIONS
|
|
// *************************************
|
|
|
|
var PickRayController = Script.require('./resources/modules/pickRayController.js?' + Date.now());
|
|
var pickRayController = new PickRayController();
|
|
|
|
/* PLAY A SOUND: Plays the specified sound at the specified position using the volume and playback
|
|
modes requested. */
|
|
var injector;
|
|
function playSound(sound, volume, position, localOnly, loop){
|
|
if (sound.downloaded) {
|
|
if (injector) {
|
|
injector.stop();
|
|
injector = null;
|
|
}
|
|
injector = Audio.playSound(sound, {
|
|
position: position,
|
|
volume: volume,
|
|
localOnly: localOnly,
|
|
loop: loop
|
|
});
|
|
}
|
|
}
|
|
|
|
// *************************************
|
|
// END UTILITY FUNCTIONS
|
|
// *************************************
|
|
|
|
/* CREATE A QUESTION MARK: Checks that question Mark does not already exist, then calculates position above
|
|
avatar's head and creates a question mark entity there */
|
|
var GROWTH_INTERVAL_MS = 2000; // takes 4 min 38 sec to reach height of 0.8 from 0.2 at ratio:1.005, interval: 1000
|
|
var MAX_HEIGHT_M = 0.8;
|
|
var GROWTH_RATIO = 1.005;
|
|
var QUESTION_MARK_PROPERTY_NAME = "Question App Mark";
|
|
var QUESTION_MARK_START_DIMENSIONS_M = { x: 0.1, y: 0.2, z: 0.02 };
|
|
var HALF = 0.5;
|
|
var Y_OFFSET_HEAD_TOP_TO_ENTITY_M = 0.1;
|
|
var questionMark;
|
|
var changingInterval;
|
|
var parentJointIndex;
|
|
var lastDimensions = QUESTION_MARK_START_DIMENSIONS_M;
|
|
function createQuestionMark() {
|
|
if (questionMark) {
|
|
return;
|
|
}
|
|
parentJointIndex = MyAvatar.getJointIndex("Hips");
|
|
if (parentJointIndex === -1) {
|
|
parentJointIndex = 0;
|
|
}
|
|
var entitySpawnPosition = MyAvatar.position;
|
|
entitySpawnPosition.y = entitySpawnPosition.y + HALF * MyAvatar.getHeight() + HALF *
|
|
QUESTION_MARK_START_DIMENSIONS_M.y + Y_OFFSET_HEAD_TOP_TO_ENTITY_M;
|
|
var questionMarkProperties = {
|
|
name: QUESTION_MARK_PROPERTY_NAME,
|
|
type: "Model",
|
|
modelURL: Script.resolvePath("resources/models/questionMark.fbx"),
|
|
lifetime: 360000,
|
|
parentID: MyAvatar.sessionUUID,
|
|
parentJointIndex: parentJointIndex,
|
|
localRotation: Quat.fromVec3Degrees({ x: 0, y: 180, z: 0 }),
|
|
position: entitySpawnPosition,
|
|
dimensions: QUESTION_MARK_START_DIMENSIONS_M,
|
|
grab: { grabbable: false },
|
|
collisionless: true
|
|
};
|
|
questionMark = Entities.addEntity(questionMarkProperties, 'avatar');
|
|
changingInterval = Script.setInterval(function() {
|
|
if (changingInterval) {
|
|
var questionMarkDimensions = Entities.getEntityProperties(questionMark, 'dimensions').dimensions;
|
|
if (questionMarkDimensions.y < MAX_HEIGHT_M) {
|
|
questionMarkDimensions = Vec3.multiply(questionMarkDimensions, GROWTH_RATIO);
|
|
var entitySpawnPosition = MyAvatar.position;
|
|
entitySpawnPosition.y = entitySpawnPosition.y + HALF * MyAvatar.getHeight() + HALF *
|
|
questionMarkDimensions.y + Y_OFFSET_HEAD_TOP_TO_ENTITY_M;
|
|
Entities.editEntity(questionMark, {
|
|
dimensions: questionMarkDimensions,
|
|
position: entitySpawnPosition
|
|
});
|
|
lastDimensions = questionMarkDimensions;
|
|
} else {
|
|
Script.clearInterval(changingInterval);
|
|
changingInterval = null;
|
|
}
|
|
}
|
|
}, GROWTH_INTERVAL_MS);
|
|
}
|
|
|
|
/* ON CLICKING APP BUTTON: (on the toolbar or tablet) if we are opening the app, play a sound and get the question mark.
|
|
If we are closing the app, remove the question mark and play a different sound */
|
|
var OPEN_SOUND = SoundCache.getSound(Script.resolvePath('resources/sounds/open.mp3'));
|
|
var OPEN_SOUND_VOLUME = 0.2;
|
|
var CLOSE_SOUND = SoundCache.getSound(Script.resolvePath('resources/sounds/close.mp3'));
|
|
var CLOSE_SOUND_VOLUME = 0.3;
|
|
var QUESTION_CHANNEL = "QuestionChannel";
|
|
var messagesReceivedConnected;
|
|
var scaleChangedConnected;
|
|
var skeletonChangedConnected;
|
|
function onClicked() {
|
|
if (questionMark) {
|
|
cleanUp();
|
|
disconnectSignalHandlers();
|
|
playSound(CLOSE_SOUND, CLOSE_SOUND_VOLUME, MyAvatar.position, true, false);
|
|
} else {
|
|
button.editProperties({ isActive: true });
|
|
playSound(OPEN_SOUND, OPEN_SOUND_VOLUME, MyAvatar.position, true, false);
|
|
createQuestionMark();
|
|
if (!messagesReceivedConnected) {
|
|
Messages.messageReceived.connect(checkMessage);
|
|
messagesReceivedConnected = true;
|
|
}
|
|
if (!scaleChangedConnected) {
|
|
MyAvatar.scaleChanged.connect(avatarScaleChanged);
|
|
scaleChangedConnected = true;
|
|
}
|
|
if (!skeletonChangedConnected) {
|
|
MyAvatar.skeletonModelURLChanged.connect(skeletonChanged);
|
|
skeletonChangedConnected = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Handles avatar being selected */
|
|
pickRayController
|
|
.registerEventHandler(selectAvatar)
|
|
.setType("avatar")
|
|
.setMapName("hifi_question")
|
|
.create();
|
|
|
|
/* SELECT AVATAR: When an admin clicks/triggers on an avatar, they are selected to speak next and their UUID
|
|
is sent out via message */
|
|
function selectAvatar(uuid, intersection) {
|
|
Messages.sendMessage(QUESTION_CHANNEL, JSON.stringify({
|
|
UUID: uuid
|
|
}));
|
|
}
|
|
|
|
/* DISCONNECT SIGNAL HANDLERS: disconnect any open signal handlers */
|
|
function disconnectSignalHandlers() {
|
|
if (messagesReceivedConnected) {
|
|
Messages.messageReceived.disconnect(checkMessage);
|
|
messagesReceivedConnected = false;
|
|
}
|
|
if (scaleChangedConnected) {
|
|
MyAvatar.scaleChanged.disconnect(avatarScaleChanged);
|
|
scaleChangedConnected = false;
|
|
}
|
|
if (skeletonChangedConnected) {
|
|
MyAvatar.skeletonModelURLChanged.disconnect(skeletonChanged);
|
|
skeletonChangedConnected = false;
|
|
}
|
|
}
|
|
|
|
/* AVATAR SELECTED: When a user is selected, the question mark disappears and a sound
|
|
plays for the selected user as their app toggles off. */
|
|
var SELECTED_SOUND = SoundCache.getSound(Script.resolvePath('resources/sounds/selected.mp3'));
|
|
var SELECTED_SOUND_VOLUME = 0.3;
|
|
function avatarSelected() {
|
|
if (questionMark) {
|
|
cleanUp();
|
|
disconnectSignalHandlers();
|
|
playSound(SELECTED_SOUND, SELECTED_SOUND_VOLUME, MyAvatar.position, true, false);
|
|
}
|
|
}
|
|
|
|
/* ON STOPPING THE SCRIPT: Disconnect signals and clean up */
|
|
function appEnding() {
|
|
cleanUp();
|
|
Messages.unsubscribe(QUESTION_CHANNEL);
|
|
Users.canKickChanged.disconnect(adminStatusCheck);
|
|
button.clicked.disconnect(onClicked);
|
|
tablet.removeButton(button);
|
|
pickRayController.destroy();
|
|
Window.domainChanged.disconnect(domainChanged);
|
|
}
|
|
|
|
/* REMOVE QUESTION MARK: Remove referenced question mark if it exists and any other strays */
|
|
function removeQuestionMarkEntities() {
|
|
if (questionMark) {
|
|
Entities.deleteEntity(questionMark);
|
|
questionMark = null;
|
|
}
|
|
MyAvatar.getAvatarEntitiesVariant().forEach(function(avatarEntity) {
|
|
var name = Entities.getEntityProperties(avatarEntity.id, 'name').name;
|
|
if (name === QUESTION_MARK_PROPERTY_NAME) {
|
|
Entities.deleteEntity(avatarEntity.id);
|
|
questionMark = null;
|
|
}
|
|
});
|
|
}
|
|
|
|
/* CLEANUP: Remove question mark, search for any unreferenced question marks to clean up */
|
|
function cleanUp() {
|
|
removeQuestionMarkEntities();
|
|
if (changingInterval) {
|
|
Script.clearInterval(changingInterval);
|
|
changingInterval = null;
|
|
}
|
|
if (injector) {
|
|
injector.stop();
|
|
injector = null;
|
|
}
|
|
button.editProperties({ isActive: false });
|
|
}
|
|
|
|
/* WHEN USER DOMAIN CHANGES: Close app to remove question mark when leaving the domain */
|
|
var WAIT_TO_CLEAN_UP_MS = 2000;
|
|
function domainChanged() {
|
|
Script.setTimeout(function() {
|
|
cleanUp();
|
|
}, WAIT_TO_CLEAN_UP_MS);
|
|
}
|
|
|
|
/* CHECK MESSAGE RECEIVED: If uuid that was broadcast matches, this user has been selected to speak next */
|
|
function checkMessage(channel, message, sender) {
|
|
if (channel === QUESTION_CHANNEL) {
|
|
try {
|
|
message = JSON.parse(message);
|
|
} catch (error) {
|
|
print("Couldn't parse message: " + error);
|
|
return;
|
|
}
|
|
if (message.UUID === MyAvatar.sessionUUID && questionMark) {
|
|
avatarSelected();
|
|
}
|
|
}
|
|
}
|
|
|
|
/* ADMIN STATUS CHANGED: User permissions have changed, set adminStatus */
|
|
var adminStatus;
|
|
function adminStatusCheck() {
|
|
adminStatus = Users.getCanKick() ? true : false;
|
|
if (adminStatus) {
|
|
pickRayController.enable();
|
|
} else {
|
|
pickRayController.disable();
|
|
}
|
|
}
|
|
|
|
/* AVATAR SCALE CHANGED: Reset question mark entity back to appropriate size */
|
|
function avatarScaleChanged() {
|
|
var questionMarkDimensions = Entities.getEntityProperties(questionMark, 'dimensions').dimensions;
|
|
var entitySpawnPosition = MyAvatar.position;
|
|
entitySpawnPosition.y = entitySpawnPosition.y + HALF * MyAvatar.getHeight() + HALF *
|
|
questionMarkDimensions.y + Y_OFFSET_HEAD_TOP_TO_ENTITY_M;
|
|
Entities.editEntity(questionMark, {
|
|
dimensions: lastDimensions,
|
|
position: entitySpawnPosition
|
|
});
|
|
}
|
|
|
|
/* AVATAR SKELETON CHANGED: Close the app if it is open */
|
|
function skeletonChanged() {
|
|
if (changingInterval) {
|
|
Script.clearInterval(changingInterval);
|
|
changingInterval = null;
|
|
}
|
|
if (injector) {
|
|
injector.stop();
|
|
injector = null;
|
|
}
|
|
button.editProperties({ isActive: false });
|
|
questionMark = null;
|
|
disconnectSignalHandlers();
|
|
playSound(CLOSE_SOUND, CLOSE_SOUND_VOLUME, MyAvatar.position, true, false);
|
|
}
|
|
|
|
Messages.subscribe(QUESTION_CHANNEL);
|
|
var tablet = Tablet.getTablet('com.highfidelity.interface.tablet.system');
|
|
var button = tablet.addButton({
|
|
text: 'QUESTION',
|
|
icon: Script.resolvePath('resources/icons/question-i.png'),
|
|
activeIcon: Script.resolvePath('resources/icons/question-a.png'),
|
|
sortOrder: 1
|
|
});
|
|
adminStatusCheck();
|
|
Users.canKickChanged.connect(adminStatusCheck);
|
|
button.clicked.connect(onClicked);
|
|
Window.domainChanged.connect(domainChanged);
|
|
Script.scriptEnding.connect(appEnding);
|
|
}());
|