content/hifi-content/Experiences/Releases/usefulUtilities/bouncerZone/2019-09-18_14-12-00/bouncerZone.js
2022-02-13 23:16:46 +01:00

225 lines
9 KiB
JavaScript

//
// ZoneScript.js
//
// This script serves as a virtual bouncer depending on username, admin status, and whether or not a client can validate
// ownership of a particular specified avatar entity.
//
// 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
/* globals Entities, WalletScriptingInterface, Window, AccountServices */
(function () {
// Enable this for debug prints in client logs.
var DEBUG = false;
var DEFAULT_USER_DATA = {
"rejectTeleportLocation" : "/0,0,0/0,0,0,0",
"whitelist" : {
"allowAdmins": false,
"marketplaceID": null,
"usernames" : [""]
},
"bounceSound": {
"bounceSoundURL": null,
"bounceSoundVolume": 1
}
};
// #region USERDATA VARIABLES
// Dynamically approved usernames
var userDataUsernameWhitelist;
// Boolean to determine if all admins are allowed. Admins are defined as users with lock/unlock privileges.
var allowAdmins;
// Used when verifying ownership of a "ticket" wearable.
var ticketMarketplaceID;
// Used when teleporting users outside the bouncer zone.
var rejectionLocation;
// Downloaded sound object of sound to play if user gets bounced
var downloadedBounceSound;
// Volume, 0-1, to play bounce sound
var bounceSoundVolume;
// #endregion USERDATA VARIABLES
// #region UTILITIES
/* PLAY A SOUND: Plays a sound at the specified position, volume, local mode, and playback
mode 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
});
}
}
// #endregion UTILITIES
// The handler for the `WalletScriptingInterface.ownershipVerificationSuccess` signal.
function ticketVerificationSuccess(entityID) {
// We don't care about entities that we didn't want to verify
if (entityID !== potentialTicketEntityID) {
return;
}
if (DEBUG) {
print("You MAY enter - verification passed for entity: " + entityID);
}
if (signalsConnected) {
WalletScriptingInterface.ownershipVerificationSuccess.disconnect(ticketVerificationSuccess);
WalletScriptingInterface.ownershipVerificationFailed.disconnect(ticketVerificationFailed);
}
}
// The handler for the `WalletScriptingInterface.ownershipVerificationFailed` signal.
// Will bounce avatars from the Bouncer Zone if the entity that failed verification
// is the one we care about.
function ticketVerificationFailed(entityID) {
// We don't care about entities that we didn't want to verify
if (entityID !== potentialTicketEntityID) {
return;
}
if (DEBUG) {
print("You MAY NOT enter - verification failed for entity: " + entityID);
}
if (signalsConnected) {
WalletScriptingInterface.ownershipVerificationSuccess.disconnect(ticketVerificationSuccess);
WalletScriptingInterface.ownershipVerificationFailed.disconnect(ticketVerificationFailed);
}
bounceAvatarFromZone();
}
// Searches around MyAvatar for a wearable whose marketplaceID matches ticketMarketplaceID.
var WEARABLE_SEARCH_RADIUS = 10;
var potentialTicketEntityID;
var signalsConnected = false;
function userHasTicketWearable() {
var currentAvatarWearableIDs = Entities.findEntitiesByType('Model', MyAvatar.position, WEARABLE_SEARCH_RADIUS);
for (var i = 0; i < currentAvatarWearableIDs.length; i++) {
var properties = Entities.getEntityProperties(currentAvatarWearableIDs[i],
['marketplaceID', 'certificateID', 'parentID']);
if (properties.marketplaceID === ticketMarketplaceID && properties.parentID === MyAvatar.sessionUUID) {
if (!signalsConnected) {
WalletScriptingInterface.ownershipVerificationSuccess.connect(ticketVerificationSuccess);
WalletScriptingInterface.ownershipVerificationFailed.connect(ticketVerificationFailed);
signalsConnected = true;
}
potentialTicketEntityID = currentAvatarWearableIDs[i];
WalletScriptingInterface.proveAvatarEntityOwnershipVerification(currentAvatarWearableIDs[i]);
return true;
}
}
return false;
}
// Updates some internal variables based on the current userData property of the attached zone entity.
function updateParametersFromUserData() {
var userDataProperty = DEFAULT_USER_DATA;
try {
userDataProperty = JSON.parse(Entities.getEntityProperties(_entityID, 'userData').userData);
} catch (err) {
console.error("Error parsing userData: ", err);
}
rejectionLocation = userDataProperty.rejectTeleportLocation || DEFAULT_USER_DATA.rejectTeleportLocation;
ticketMarketplaceID = userDataProperty.whitelist && userDataProperty.whitelist.marketplaceID ||
DEFAULT_USER_DATA.whitelist.marketplaceID;
userDataUsernameWhitelist = userDataProperty.whitelist && userDataProperty.whitelist.usernames ||
DEFAULT_USER_DATA.whitelist.usernames;
allowAdmins = userDataProperty.whitelist && userDataProperty.whitelist.allowAdmins ||
DEFAULT_USER_DATA.whitelist.allowAdmins;
var bounceSoundURL = userDataProperty.bounceSound && userDataProperty.bounceSound.bounceSoundURL ||
DEFAULT_USER_DATA.bounceSound.bounceSoundURL;
if (userDataProperty.bounceSound && (typeof userDataProperty.bounceSound.bounceSoundVolume === "number")) {
bounceSoundVolume = Math.max(0, userDataProperty.bounceSound.bounceSoundVolume);
} else {
bounceSoundVolume = DEFAULT_USER_DATA.bounceSound.bounceSoundVolume;
}
downloadedBounceSound = bounceSoundURL && SoundCache.getSound(bounceSoundURL) || null;
}
// Moves my avatar to `rejectionLocation`
function bounceAvatarFromZone() {
if (DEBUG) {
print("Rejected from zone to: ", rejectionLocation);
}
if (downloadedBounceSound) {
playSound(downloadedBounceSound, bounceSoundVolume, MyAvatar.position, true, false);
}
Window.location = rejectionLocation;
}
var _entityID; // The Bouncer Zone entity ID.
var LOAD_TIME_MS = 50; // A small delay to ensure that all internal variables are loaded from userData.
var _this = this;
// save this zone ID
_this.preload = function(entityID) {
_entityID = entityID;
},
// Performs the various bouncer checks to determine whether to do nothing
// or bounce an avatar from the Bouncer Zone
_this.performBouncerChecks = function() {
// Determines whether or not my username is on the whitelist populated by userData.
function isOnUserDataUsernameWhitelist() {
var currentUsername = AccountServices.username.toLowerCase();
for (var i = 0; i < userDataUsernameWhitelist.length; i++) {
if (userDataUsernameWhitelist[i].toLowerCase() === currentUsername) {
if (DEBUG) {
print("Username is on userData whitelist");
}
return true;
}
}
return false;
}
if (!isOnUserDataUsernameWhitelist() && !(allowAdmins && Entities.canAdjustLocks()) &&
!(ticketMarketplaceID && userHasTicketWearable())) {
bounceAvatarFromZone();
}
},
// Updates various internal variables from userData, waits a small bit, then performs bouncer checks.
_this.updateParametersThenPerformChecks = function() {
updateParametersFromUserData();
Script.setTimeout(_this.performBouncerChecks, LOAD_TIME_MS);
},
// Fires when entering the Bouncer Zone entity
_this.enterEntity = function() {
if (DEBUG) {
print("YOU'RE IN THE ZONE!");
}
_this.updateParametersThenPerformChecks();
};
// disconnect signals if necessary
_this.unload = function() {
if (signalsConnected) {
WalletScriptingInterface.ownershipVerificationSuccess.disconnect(ticketVerificationSuccess);
WalletScriptingInterface.ownershipVerificationFailed.disconnect(ticketVerificationFailed);
}
};
});