"use strict"; // // emote.js // scripts/system/ // // Created by Brad Hefta-Gaub on 7 Jan 2018 // Copyright 2018 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 // /* globals Script, Tablet */ /* eslint indent: ["error", 4, { "outerIIFEBody": 0 }] */ (function() { // BEGIN LOCAL_SCOPE var controllerStandard = Controller.Standard; var EMOTE_ANIMATIONS = ['Crying', 'Surprised', 'Dancing', 'Cheering', 'Waving', 'Fall', 'Pointing', 'Clapping', 'Sit1', 'Sit2', 'Sit3', 'Love']; var ANIMATIONS = Array(); var eventMappingName = "io.highfidelity.away"; // restoreAnimation on hand controller button events, too var eventMapping = Controller.newMapping(eventMappingName); EMOTE_ANIMATIONS.forEach(function (name) { var animationURL = Script.resolvePath("assets/animations/" + name + ".fbx"); var resource = AnimationCache.prefetch(animationURL); var animation = AnimationCache.getAnimation(animationURL); ANIMATIONS[name] = { url: animationURL, animation: animation, resource: resource}; }); var EMOTE_APP_BASE = "html/EmoteApp.html"; var EMOTE_APP_URL = Script.resolvePath(EMOTE_APP_BASE); var EMOTE_LABEL = "EMOTE"; var EMOTE_APP_SORT_ORDER = 12; var FPS = 60; var MSEC_PER_SEC = 1000; var FINISHED = 3; // see ScriptableResource::State var onEmoteScreen = false; var button; var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); var activeTimer = false; // Used to cancel active timer if a user plays an animation while another animation is playing var activeEmote = false; // To keep track of the currently playing emote button = tablet.addButton({ icon: "icons/tablet-icons/emote-i.svg", activeIcon: "icons/tablet-icons/emote-a.svg", text: EMOTE_LABEL, sortOrder: EMOTE_APP_SORT_ORDER }); function onClicked() { if (onEmoteScreen) { tablet.gotoHomeScreen(); } else { onEmoteScreen = true; tablet.gotoWebScreen(EMOTE_APP_URL); } } function onScreenChanged(type, url) { onEmoteScreen = type === "Web" && (url.indexOf(EMOTE_APP_BASE) === url.length - EMOTE_APP_BASE.length); button.editProperties({ isActive: onEmoteScreen }); } // Handle the events we're receiving from the web UI function onWebEventReceived(event) { // Converts the event to a JavasScript Object if (typeof event === "string") { event = JSON.parse(event); } if (event.type === "click") { // Allow for a random sitting animation when a user selects sit var randSit = Math.floor(Math.random() * 3) + 1; var emoteName = event.data; if (emoteName === "Sit"){ emoteName = event.data + randSit; // Sit1, Sit2, Sit3 } if (ANIMATIONS[emoteName].resource.state === FINISHED) { if (activeTimer !== false) { Script.clearTimeout(activeTimer); } // If the activeEmote is different from the chosen emote, then play the new emote // This is a second click on the same emote as the activeEmote, and we will just stop it if (activeEmote !== emoteName) { activeEmote = emoteName; // Sit is the only animation currently that plays and then ends at the last frame if (emoteName.match(/^Sit.*$/)) { // If user provides input during a sit, the avatar animation state should be restored Controller.keyPressEvent.connect(restoreAnimation); Controller.enableMapping(eventMappingName); MyAvatar.overrideAnimation(ANIMATIONS[emoteName].url, FPS, false, 0, frameCount); } else { activeEmote = emoteName; var frameCount = ANIMATIONS[emoteName].animation.frames.length; MyAvatar.overrideAnimation(ANIMATIONS[emoteName].url, FPS, false, 0, frameCount); var timeOut = MSEC_PER_SEC * frameCount / FPS; activeTimer = Script.setTimeout(function () { MyAvatar.restoreAnimation(); activeTimer = false; activeEmote = false; }, timeOut); } } else { activeEmote = false; MyAvatar.restoreAnimation(); } } } } // Restore the navigation animation states (idle, walk, run) function restoreAnimation() { MyAvatar.restoreAnimation(); // Make sure the input is disconnected after animations are restored so it doesn't affect any emotes other than sit Controller.keyPressEvent.disconnect(restoreAnimation); Controller.disableMapping(eventMappingName); } // Note peek() so as to not interfere with other mappings. eventMapping.from(controllerStandard.LeftPrimaryThumb).peek().to(restoreAnimation); eventMapping.from(controllerStandard.RightPrimaryThumb).peek().to(restoreAnimation); eventMapping.from(controllerStandard.LeftSecondaryThumb).peek().to(restoreAnimation); eventMapping.from(controllerStandard.RightSecondaryThumb).peek().to(restoreAnimation); eventMapping.from(controllerStandard.LB).peek().to(restoreAnimation); eventMapping.from(controllerStandard.LS).peek().to(restoreAnimation); eventMapping.from(controllerStandard.RY).peek().to(restoreAnimation); eventMapping.from(controllerStandard.RX).peek().to(restoreAnimation); eventMapping.from(controllerStandard.LY).peek().to(restoreAnimation); eventMapping.from(controllerStandard.LX).peek().to(restoreAnimation); eventMapping.from(controllerStandard.LeftGrip).peek().to(restoreAnimation); eventMapping.from(controllerStandard.RB).peek().to(restoreAnimation); eventMapping.from(controllerStandard.RS).peek().to(restoreAnimation); eventMapping.from(controllerStandard.RightGrip).peek().to(restoreAnimation); eventMapping.from(controllerStandard.Back).peek().to(restoreAnimation); eventMapping.from(controllerStandard.Start).peek().to(restoreAnimation); button.clicked.connect(onClicked); tablet.screenChanged.connect(onScreenChanged); tablet.webEventReceived.connect(onWebEventReceived); Script.scriptEnding.connect(function () { if (onEmoteScreen) { tablet.gotoHomeScreen(); } button.clicked.disconnect(onClicked); tablet.screenChanged.disconnect(onScreenChanged); if (tablet) { tablet.removeButton(button); } if (activeTimer !== false) { Script.clearTimeout(activeTimer); MyAvatar.restoreAnimation(); } }); }()); // END LOCAL_SCOPE