"use strict"; // // mod.js // scripts/system/ // // Created by Stephen Birarda on July 11th, 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 Toolbars, Script, Users, Overlays, AvatarList, Controller, Camera, getControllerWorldLocation */ (function() { // BEGIN LOCAL_SCOPE Script.include("/~/system/libraries/controllers.js"); var controllerStandard = Controller.Standard; // grab the toolbar var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); var ASSETS_PATH = Script.resolvePath("assets"); var TOOLS_PATH = Script.resolvePath("assets/images/tools/"); function buttonImageURL() { return (Users.canKick ? "kick.svg" : "ignore.svg"); } // setup the mod button and add it to the toolbar var button = tablet.addButton({ icon: "icons/tablet-icons/ignore-i.svg", text: "KICK" }); // if this user's kick permissions change, change the state of the button in the HUD Users.canKickChanged.connect(function(canKick){ button.editProperties({text: buttonImageURL()}); }); var isShowingOverlays = false; var modOverlays = {}; function removeOverlays() { // enumerate the overlays and remove them var modOverlayKeys = Object.keys(modOverlays); for (var i = 0; i < modOverlayKeys.length; ++i) { var avatarID = modOverlayKeys[i]; for (var j = 0; j < modOverlays[avatarID].length; ++j) { Entities.deleteEntity(modOverlays[avatarID][j]); } } modOverlays = {}; } // handle clicks on the toolbar button function buttonClicked(){ if (isShowingOverlays) { removeOverlays(); isShowingOverlays = false; } else { isShowingOverlays = true; } } button.clicked.connect(buttonClicked); function kickOverlayURL() { return ASSETS_PATH + "/images/" + (Users.canKick ? "kick-target.svg" : "ignore-target.svg"); } function muteOverlayURL() { return ASSETS_PATH + "/images/" + "mute-target.svg"; } function updateOverlays() { if (isShowingOverlays) { var identifiers = AvatarList.getAvatarIdentifiers(); for (var i = 0; i < identifiers.length; ++i) { var avatarID = identifiers[i]; if (avatarID === null) { // this is our avatar, skip it continue; } // get the position for this avatar var avatar = AvatarList.getAvatar(avatarID); var avatarPosition = avatar && avatar.position; if (!avatarPosition) { // we don't have a valid position for this avatar, skip it continue; } // setup a position for the overlay that is just above this avatar's head var kickOverlayPosition = avatar.getJointPosition("Head"); kickOverlayPosition.y += 0.45; var muteOverlayPosition = avatar.getJointPosition("Head"); muteOverlayPosition.y += 0.70; if (avatarID in modOverlays) { // keep the overlay above the current position of this avatar Entities.editEntity(modOverlays[avatarID][0], { "position": kickOverlayPosition, "imageURL": kickOverlayURL() }); if (Users.canKick) { Entities.editEntity(modOverlays[avatarID][1], { "position": muteOverlayPosition, "imageURL": muteOverlayURL() }); } } else { // add the overlay above this avatar var newKickOverlay = Entities.addEntity({ "type": "Image", "imageURL": kickOverlayURL(), "position": kickOverlayPosition, "dimensions": { "x": 0.4, "y": 0.4, "z": 0.4}, "color": { "red": 255, "green": 255, "blue": 255}, "alpha": 1, "primitiveMode": "solid", "billboardMode": "full", "renderLayer": "front" }, "local"); modOverlays[avatarID]=[newKickOverlay]; if (Users.canKick) { var newMuteOverlay = Entities.addEntity({ "type": "Image", "imageURL": muteOverlayURL(), "position": muteOverlayPosition, "dimensions": { "x": 0.4, "y": 0.4, "z": 0.4}, "color": { "red": 255, "green": 255, "blue": 255}, "alpha": 1, "primitiveMode": "solid", "billboardMode": "full", "renderLayer": "front" }, "local"); // push this overlay to our array of overlays modOverlays[avatarID].push(newMuteOverlay); } } } } } Script.update.connect(updateOverlays); AvatarList.avatarRemovedEvent.connect(function(avatarID){ if (isShowingOverlays) { // we are currently showing overlays and an avatar just went away // first remove the rendered overlays for (var j = 0; j < modOverlays[avatarID].length; ++j) { Entities.deleteEntity(modOverlays[avatarID][j]); } // delete the saved ID of the overlay from our mod overlays object delete modOverlays[avatarID]; } }); function handleSelectedOverlay(clickedOverlay) { // see this is one of our mod overlays var modOverlayKeys = Object.keys(modOverlays); for (var i = 0; i < modOverlayKeys.length; ++i) { var avatarID = modOverlayKeys[i]; var modOverlay = modOverlays[avatarID][0]; var muteOverlay = modOverlays[avatarID][1]; if (clickedOverlay.overlayID == modOverlay) { // matched to an overlay, ask for the matching avatar to be kicked or ignored if (Users.canKick) { Users.kick(avatarID); } else { Users.ignore(avatarID); } // cleanup of the overlay is handled by the connection to avatarRemovedEvent } else if (muteOverlay && clickedOverlay.overlayID == muteOverlay) { Users.mute(avatarID); } } } Controller.mousePressEvent.connect(function(event){ if (isShowingOverlays) { // handle click events so we can detect when our overlays are clicked if (!event.isLeftButton) { // if another mouse button than left is pressed ignore it return false; } // compute the pick ray from the event var pickRay = Camera.computePickRay(event.x, event.y); // grab the clicked overlay for the given pick ray var clickedOverlay = Overlays.findRayIntersection(pickRay); if (clickedOverlay.intersects) { handleSelectedOverlay(clickedOverlay); } } }); // We get mouseMoveEvents from the handControllers, via handControllerPointer. // But we dont' get mousePressEvents. var triggerMapping = Controller.newMapping(Script.resolvePath('') + '-click'); function controllerComputePickRay(hand) { var controllerPose = getControllerWorldLocation(hand, true); if (controllerPose.valid) { return { origin: controllerPose.position, direction: Quat.getUp(controllerPose.orientation) }; } } function makeClickHandler(hand) { return function(clicked) { if (clicked == 1.0 && isShowingOverlays) { var pickRay = controllerComputePickRay(hand); if (pickRay) { var overlayIntersection = Overlays.findRayIntersection(pickRay); if (overlayIntersection.intersects) { handleSelectedOverlay(overlayIntersection); } } } }; } triggerMapping.from(controllerStandard.RTClick).peek().to(makeClickHandler(controllerStandard.RightHand)); triggerMapping.from(controllerStandard.LTClick).peek().to(makeClickHandler(controllerStandard.LeftHand)); triggerMapping.enable(); // cleanup the toolbar button and overlays when script is stopped Script.scriptEnding.connect(function() { tablet.removeButton(button); removeOverlays(); triggerMapping.disable(); }); }()); // END LOCAL_SCOPE