// // 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 2017 High Fidelity, Inc. // // Set Up: // 1. Add below userData object to zone entity userData // 1. Fill in rejectTeleportLocation, example "/13.9828,-10.5277,0.0609192/0,0.460983,0,0.887409" // 2. Optional: add marketplaceID of the item to verify // 3. Optional: (can update while script is running): each username to add to whitelist // 2. Add approved users to APPROVED_USERNAMES below, keep blank if not using // 3. Add script to zone entity // 4. Update userData at anytime to add more to usernames your whitelist // // Add this to the zone userData : // { // "whitelist" : { // "rejectTeleportLocation" : <> // "marketplaceID" : <>, // "usernames" : [] // }, // "grabbableKey": { // "grabbable": false // } // } // // whitelist - (required) contains variables for the zone // rejectTeleportLocation - (required) rejected avatars are sent to these domain coordinates // marketplaceID - (optional) marketplace item id for marketplace item verification // usernames - (optional) array for usernames to be added while script is running // // 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, Wallet, Window, AccountServices */ (function () { // username lookup variables var APPROVED_USERNAMES = ["philip",]; // hardcoded var whitelist = []; // stores lowercase usernames from APPROVED_USERNAMES // usernames inside userData var _usernames; // userData names var checkUserDataInterval; // marketplace lookup variables var WEARABLE_SEARCH_RADIUS = 10; var foundValidTestable = false; var _foundEntityID = -1; var _passMarketplaceID; var _userDataProperties; var _backupLocation; var _entityID; var LOAD_TIME = 50; var avatarCheckStep = 0; var HALF = 0.5; var DEBUG = false; var marketplaceItem = { verificationSuccess: function (entityID) { if (DEBUG) { print("You may enter - verification passed for entity: " + entityID); } Wallet.ownershipVerificationSuccess.disconnect(this.verificationSuccess); Wallet.ownershipVerificationFailed.disconnect(this.verificationFailed); }, verificationFailed: function (entityID) { if (DEBUG) { print("You may not enter - verification failed for entity: " + entityID); } utils.rejectTeleportAvatar(); Wallet.ownershipVerificationSuccess.disconnect(this.verificationSuccess); Wallet.ownershipVerificationFailed.disconnect(this.verificationFailed); }, verifyAvatarOwnership: function (entityID) { Wallet.proveAvatarEntityOwnershipVerification(entityID); }, searchForMatchingItem: function () { Entities.findEntitiesByType('Model', MyAvatar.position, WEARABLE_SEARCH_RADIUS).forEach(function (entityID) { var properties = Entities.getEntityProperties(entityID, ['marketplaceID', 'certificateID', 'parentID']); if (properties.marketplaceID === _passMarketplaceID && properties.parentID === MyAvatar.sessionUUID) { _foundEntityID = entityID; foundValidTestable = true; this.verifyAvatarOwnership(_foundEntityID); Wallet.ownershipVerificationSuccess.connect(this.verificationSuccess); Wallet.ownershipVerificationFailed.connect(this.verificationFailed); } }); if (!foundValidTestable) { utils.rejectTeleportAvatar(); } } }; var avatarUserName = { isOnWhitelist: function () { var username = AccountServices.username.toLowerCase(); if (whitelist.indexOf(username) >= 0) { if (DEBUG) { print("Username is on hardcoded whitelist"); } return true; } else { return false; } }, isInUserData: function () { var username = AccountServices.username.toLowerCase(); for (var i = 0; i < _usernames.length; i++) { if (_usernames[i].toLowerCase() === username) { if (DEBUG) { print("Username is on userData whitelist"); } return true; } } return false; } }; var utils = { updateUserData: function () { try { _userDataProperties = JSON.parse(Entities.getEntityProperties(_entityID, 'userData').userData); } catch (err) { console.error("Error parsing userData: ", err); } _usernames = _userDataProperties.whitelist && _userDataProperties.whitelist.usernames || []; }, rejectTeleportAvatar: function () { if (DEBUG) { print("Rejected from zone to: ", _backupLocation); } Window.location.handleLookupString(_backupLocation); }, largestAxisVec: function (dimensions) { var max = Math.max(dimensions.x, dimensions.y, dimensions.z); return max; }, isInEntity: function () { 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 ProtectedZone = function () { }; ProtectedZone.prototype = { preload: function (entityID) { _entityID = entityID; var _this = this; if (APPROVED_USERNAMES.length > 0) { APPROVED_USERNAMES.forEach(function (username) { whitelist.push(username.toLowerCase()); }); } utils.updateUserData(); Script.setTimeout(function () { if (_userDataProperties.whitelist) { _passMarketplaceID = _userDataProperties.whitelist.marketplaceID || ""; _backupLocation = _userDataProperties.whitelist.rejectTeleportLocation; _usernames = _userDataProperties.whitelist.usernames || []; } _this.insideEntityCheck(); }, LOAD_TIME); }, insideEntityCheck: function () { // ensures every avatar experiences the enterEntity method var properties = Entities.getEntityProperties(_entityID, ["position", "dimensions"]); var largestDimension = utils.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: ", avatarCheckStep); } // do isInZone check if (utils.isInEntity()) { this.enterEntity(); } } }, enterEntity: function () { utils.updateUserData(); Script.setTimeout(function () { var isInUserData = avatarUserName.isInUserData(); if (isInUserData || (APPROVED_USERNAMES.length > 0 && avatarUserName.isOnWhitelist())) { // do nothing } else { // did not pass username tests if (_passMarketplaceID) { // if marketplaceID exists look for item foundValidTestable = false; marketplaceItem.searchForMatchingItem(); // will reject within function } else { // otherwise reject avatar utils.rejectTeleportAvatar(); } } }, LOAD_TIME); }, unload: function () { if (checkUserDataInterval) { Script.clearInterval(checkUserDataInterval); } } }; return new ProtectedZone(); });