248 lines
9.5 KiB
JavaScript
248 lines
9.5 KiB
JavaScript
//
|
|
// ZoneScript.js
|
|
//
|
|
// This script serves as a virtual bouncer depending on username or whether or not a client can validate
|
|
// ownership of a particular specified avatar entity. Can one or all three methods: hardcoded list in APPROVED_USERNAMES,
|
|
// inside entity userData username list, and/or verifying an wearable marketplace entity through it's ID.
|
|
//
|
|
// 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 () {
|
|
// Hardcoded approved usernames
|
|
var APPROVED_USERNAMES = ["philip", "ryan"];
|
|
// Dynamically approved usernames
|
|
var userDataUsernameWhitelist;
|
|
|
|
// Used when verifying ownership of a "ticket" wearable.
|
|
// Set in zone entity's userData.
|
|
var ticketMarketplaceID = "";
|
|
|
|
// Used when teleporting users outside the bouncer zone.
|
|
// Set in userData
|
|
var rejectionLocation;
|
|
|
|
// Enable this for debug prints in client logs.
|
|
var DEBUG = true;
|
|
|
|
// 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);
|
|
}
|
|
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);
|
|
}
|
|
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;
|
|
function searchForTicketWearable() {
|
|
potentialTicketEntityID = "";
|
|
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) {
|
|
WalletScriptingInterface.ownershipVerificationSuccess.connect(ticketVerificationSuccess);
|
|
WalletScriptingInterface.ownershipVerificationFailed.connect(ticketVerificationFailed);
|
|
potentialTicketEntityID = currentAvatarWearableIDs[i];
|
|
WalletScriptingInterface.proveAvatarEntityOwnershipVerification(currentAvatarWearableIDs[i]);
|
|
return;
|
|
}
|
|
}
|
|
|
|
bounceAvatarFromZone();
|
|
}
|
|
|
|
// Updates some internal variables based on the current userData property of the attached zone entity.
|
|
function updateParametersFromUserData() {
|
|
var userDataProperty = {
|
|
"whitelist" : {
|
|
"rejectTeleportLocation" : "/",
|
|
"marketplaceID" : "",
|
|
"usernames" : [""]
|
|
}
|
|
};
|
|
|
|
try {
|
|
userDataProperty = JSON.parse(Entities.getEntityProperties(_entityID, 'userData').userData);
|
|
} catch (err) {
|
|
console.error("Error parsing userData: ", err);
|
|
}
|
|
|
|
ticketMarketplaceID = userDataProperty.whitelist.marketplaceID || "";
|
|
rejectionLocation = userDataProperty.whitelist.rejectTeleportLocation;
|
|
userDataUsernameWhitelist = userDataProperty.whitelist && userDataProperty.whitelist.usernames || [];
|
|
}
|
|
|
|
// Moves my avatar to `rejectionLocation`
|
|
function bounceAvatarFromZone() {
|
|
if (DEBUG) {
|
|
print("Rejected from zone to: ", rejectionLocation);
|
|
}
|
|
Window.location.handleLookupString(rejectionLocation);
|
|
}
|
|
|
|
// Returns true if my avatar is inside the bouncer zone, false otherwise.
|
|
var HALF = 0.5;
|
|
function avatarIsInsideBouncerZone() {
|
|
var properties = Entities.getEntityProperties(_entityID, ["position", "dimensions", "rotation"]);
|
|
var position = properties.position;
|
|
var dimensions = properties.dimensions;
|
|
|
|
var avatarPosition = MyAvatar.position;
|
|
var worldOffset = Vec3.subtract(avatarPosition, position);
|
|
|
|
avatarPosition = Vec3.multiplyQbyV(Quat.inverse(properties.rotation), worldOffset);
|
|
|
|
var minX = 0 - dimensions.x * HALF;
|
|
var maxX = 0 + dimensions.x * HALF;
|
|
var minY = 0 - dimensions.y * HALF;
|
|
var maxY = 0 + dimensions.y * HALF;
|
|
var minZ = 0 - dimensions.z * HALF;
|
|
var maxZ = 0 + dimensions.z * HALF;
|
|
|
|
if (avatarPosition.x >= minX && avatarPosition.x <= maxX
|
|
&& avatarPosition.y >= minY && avatarPosition.y <= maxY
|
|
&& avatarPosition.z >= minZ && avatarPosition.z <= maxZ) {
|
|
|
|
if (DEBUG) {
|
|
print("Avatar IS inside zone");
|
|
}
|
|
return true;
|
|
|
|
} else {
|
|
if (DEBUG) {
|
|
print("Avatar IS NOT in zone");
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
|
|
|
|
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;
|
|
|
|
|
|
_this.preload = function(entityID) {
|
|
_entityID = entityID;
|
|
|
|
_this.initialBounceCheck();
|
|
},
|
|
|
|
// Ensures that every avatar experiences the enterEntity method, even if they were already in the entity
|
|
// when the script started.
|
|
_this.initialBounceCheck = function() {
|
|
function largestAxisVec(dimensions) {
|
|
var max = Math.max(dimensions.x, dimensions.y, dimensions.z);
|
|
return max;
|
|
}
|
|
var properties = Entities.getEntityProperties(_entityID, ["position", "dimensions"]);
|
|
var largestDimension = largestAxisVec(properties.dimensions);
|
|
var avatarsInRange = AvatarList.getAvatarsInRange(properties.position, largestDimension).filter(function(id) {
|
|
return id === MyAvatar.sessionUUID;
|
|
});
|
|
|
|
if (avatarsInRange.length > 0) {
|
|
if (DEBUG) {
|
|
print("Found avatar near zone");
|
|
}
|
|
// do isInZone check
|
|
if (avatarIsInsideBouncerZone()) {
|
|
_this.enterEntity();
|
|
}
|
|
}
|
|
},
|
|
|
|
// 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;
|
|
}
|
|
|
|
// Determines whether or not my username is on the whitelist
|
|
// populated at the top of this script.
|
|
function isOnHardcodedWhitelist() {
|
|
if (APPROVED_USERNAMES.length === 0) {
|
|
return false;
|
|
}
|
|
|
|
var currentUsername = AccountServices.username.toLowerCase();
|
|
var lowerCaseHardcodedUsernames = APPROVED_USERNAMES.map(function(value) {
|
|
return value.toLowerCase();
|
|
});
|
|
|
|
if (lowerCaseHardcodedUsernames.indexOf(currentUsername) >= 0) {
|
|
if (DEBUG) {
|
|
print("Username is on hardcoded whitelist");
|
|
}
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (isOnUserDataUsernameWhitelist() || isOnHardcodedWhitelist()) {
|
|
// Do nothing; allow the avatar to stay in the Bouncer Zone
|
|
} else if (ticketMarketplaceID !== "") {
|
|
// If ticketMarketplaceID is defined, start by searching my avatar for the ticket wearable
|
|
searchForTicketWearable();
|
|
} else {
|
|
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() {
|
|
_this.updateParametersThenPerformChecks();
|
|
}
|
|
});
|