// // checkoutZone.js // // Created by Rebecca Stankus on 9/29/17. // Copyright 2017 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 // // This zone will provide an area at which a user may purchase an item. When the avatar enters the zone wearing a // marketplace item, the item will appear as a small overlay. Scanning the overlay will cause the // the tablet to open to the marketplace home page for that item, allowing the user to quickly make the purchase. /* global Render, Wallet */ (function () { var mini = false; var SHARED = Script.require('../attachmentZoneShared.js'); var MAX_ITEMS = 12; var HALF = 0.5; var OVERLAY_PREFIX = 'MP'; var TRANSFORMS_SETTINGS = 'io.highfidelity.avatarStore.checkOut.transforms'; var ENTER_ZONE_SOUND = SoundCache.getSound(Script.resolvePath("../sounds/sound5.wav")); var APP_NAME = "CHECKOUT"; var APP_URL = "https://hifi-content.s3.amazonaws.com/rebecca/CheckoutZone/CheckoutWelcome.html"; var APP_ICON = "https://hifi-content.s3.amazonaws.com/rebecca/CheckoutZone/shoppingCart.svg"; var OVERLAY_ROTATIONAL_OFFSET = { x: 10, y: 140, z: 0 }; var TABLET = Tablet.getTablet("com.highfidelity.interface.tablet.system"); var TABLET_ROTATIONAL_OFFSET = { x: 10, y: 220, z: 0 }; var MARKETPLACE_WALLET_QML_PATH = Script.resourcesPath() + "qml/hifi/commerce/wallet/Wallet.qml"; // Milliseconds var TRANSLATION_CHECK_INTERVAL = 100; var SHORTER_STOP_TIMEOUT = 1000; var TRANSLATION_STOP_TIMEOUT = 5000; var itemHeight; var tabletLocalOffset; var _this = this; var isInZone = false; var tableProperties, tableHeight, tableLength, tableID, spawnZ, spawnY, spawnX; var zoneID; var replicaList = []; var button; var recycleBinID; var scannerZone; var replicaStoredTransforms = {}; var yOffset, zOffset, xOffset; var scannerPosition; this.preload = function(entityID) { zoneID = entityID; var sizeLimit = 1; if (Entities.getEntityProperties(zoneID, 'dimensions.x').dimensions.x < sizeLimit) { mini = true; } if (mini) { itemHeight = 0.04; tabletLocalOffset = { x: -0.01, y: 0.53, z: -0.4 }; } else { itemHeight = 0.07; tabletLocalOffset = { x: -0.1, y: 0.74, z: -0.35 }; } }; var getTransformForMarketplaceItems = function() { return Settings.getValue(TRANSFORMS_SETTINGS, {}); }; var getTransformsForMarketplaceItem = function(marketplaceID) { var transformItems = getTransformForMarketplaceItems(); if (transformItems[marketplaceID] === undefined) { return { certificateTransforms: {}, unsortedTransforms: [], lastUsedUnsortedTransformIndex: -1 }; } return transformItems[marketplaceID]; }; var addTransformForMarketplaceItem = function(marketplaceID, certificateID, transform) { if (marketplaceID === undefined) { return; } var marketplaceItemTransforms = getTransformForMarketplaceItems(); var marketplaceItemTransform = getTransformsForMarketplaceItem(marketplaceID); if (certificateID !== undefined) { marketplaceItemTransform.certificateTransforms[certificateID] = transform; } else { marketplaceItemTransform.unsortedTransforms.push(transform); } marketplaceItemTransforms[marketplaceID] = marketplaceItemTransform; Settings.setValue(TRANSFORMS_SETTINGS, marketplaceItemTransforms); }; var collectZoneData = (function(){ var zoneChildren = Entities.getChildrenIDs(zoneID); zoneChildren.forEach(function (childID) { var name = Entities.getEntityProperties(childID, 'name').name; if (name === "Checkout Table") { tableProperties = Entities.getEntityProperties(childID, ['id', 'position', 'dimensions', 'rotation']); tableID = tableProperties.id; tableHeight = tableProperties.dimensions.y; tableLength = tableProperties.dimensions.x; var halfTableHeight = HALF * tableHeight; if (mini) { yOffset = 0.2; zOffset = 0; xOffset = 0; } else { yOffset = 0; zOffset = 0.3; xOffset = -0.05; } spawnY = halfTableHeight + yOffset; var halfTableLength = HALF * tableLength; spawnZ = (halfTableLength - itemHeight + zOffset); spawnX = xOffset; return; } }); var tableChildren = Entities.getChildrenIDs(tableID); tableChildren.forEach(function (childID) { var tableChildName = Entities.getEntityProperties(childID, 'name').name; if (tableChildName === "Checkout Recycle") { recycleBinID = childID; return; } else if (tableChildName === "Checkout Scan Zone") { scannerZone = childID; scannerPosition = Entities.getEntityProperties(scannerZone, 'position').position; return; } }); }); // Spawn a copy of each attachment scaled down to fit the ITEM_HEIGHT and place it on the checkout table var spawnOverlayReplica = (function(entityID) { var entityProperties = Entities.getEntityProperties(entityID, [ 'name', 'modelURL', 'type', 'dimensions', 'marketplaceID', 'localPosition', 'localRotation', 'parentJointIndex', 'userData' ]); var overlayProperties = { url: entityProperties.modelURL, name: OVERLAY_PREFIX + entityProperties.marketplaceID, alpha: true, grabbable: true, parentID: tableID, localPosition: {x: spawnX, y: spawnY, z: spawnZ}, localRotation: Quat.fromVec3Degrees(OVERLAY_ROTATIONAL_OFFSET), // clone dimensions so we can alter it without messing up the original entities dimensions dimensions: entityProperties.dimensions }; var scale = (itemHeight / overlayProperties.dimensions.y); if ((overlayProperties.dimensions.x > itemHeight) || (overlayProperties.dimensions.y > itemHeight) || (overlayProperties.dimensions.y > itemHeight)) { overlayProperties.dimensions.y = itemHeight; overlayProperties.dimensions.x *= scale; overlayProperties.dimensions.z *= scale; } // check that the item is not too large var maxItemSize; if (mini) { maxItemSize = 0.06; } else { maxItemSize = 0.1; } var scaleReduction = 0.95; while (overlayProperties.dimensions.x > maxItemSize || overlayProperties.dimensions.z > maxItemSize || overlayProperties.dimensions.y > maxItemSize) { overlayProperties.dimensions.y *= scaleReduction; overlayProperties.dimensions.x *= scaleReduction; overlayProperties.dimensions.z *= scaleReduction; } var replica = Overlays.addOverlay("model", overlayProperties); var userDataObject = JSON.parse(entityProperties.userData); userDataObject.replicaOverlayID = replica; Entities.editEntity(entityID, {userData: JSON.stringify(userDataObject)}); var replicaStoredTransform = { position: entityProperties.localPosition, rotation: entityProperties.localRotation, dimensions: entityProperties.dimensions, jointName: MyAvatar.jointNames[entityProperties.parentJointIndex], demoEntityID: entityID }; replicaStoredTransforms[replica] = replicaStoredTransform; replicaList.push(replica); var mousePress = function(id, event) { if (replicaList.indexOf(id) !== -1) { Overlays.editOverlay(id, {position: scannerPosition}); } }; Overlays.mousePressOnOverlay.connect(mousePress); }); _this.replicaCheckedOut = function(entityID, args) { var ARGS_INDEX = { REPLICA_OVERLAY: 0, NEW_ENTITY: 1 }; var replicaOverlayID = args[ARGS_INDEX.REPLICA_OVERLAY]; var newEntityID = args[ARGS_INDEX.NEW_ENTITY]; // Delete the new entity when the transforms are not found. if (replicaStoredTransforms[replicaOverlayID] === undefined) { print('Could not find transform data, deleting purchased entity.'); Entities.deleteEntity(newEntityID); return; } var transform = replicaStoredTransforms[replicaOverlayID]; var transformProperties = { parentID: MyAvatar.sessionUUID, parentJointIndex: MyAvatar.getJointIndex(transform.jointName), localPosition: transform.position, localRotation: transform.rotation, dimensions: transform.dimensions, velocity: {x: 0, y: 0, z: 0}, dynamic: false }; Entities.editEntity(newEntityID, transformProperties); // Make really sure that the translations are set properly var makeSureInterval = Script.setInterval(function() { Entities.editEntity(newEntityID, transformProperties); }, TRANSLATION_CHECK_INTERVAL); // Five seconds should be enough to be sure, otherwise we have a problem Script.setTimeout(function() { makeSureInterval.stop(); }, TRANSLATION_STOP_TIMEOUT); var newEntityProperties = Entities.getEntityProperties(newEntityID, ['marketplaceID', 'certificateID']); var certificateID = undefined; if (newEntityProperties.certificateID !== "" && newEntityProperties.certificateID !== undefined) { certificateID = newEntityProperties.certificateID; } addTransformForMarketplaceItem(newEntityProperties.marketplaceID, certificateID, transform); // Remove the demo object, to prevent overlapping objects Entities.deleteEntity(transform.demoEntityID); }; var setupApp = (function() { button = TABLET.addButton({ icon: APP_ICON, text: APP_NAME }); HMD.openTablet(true); function onClicked() { TABLET.gotoWebScreen(APP_URL); } print("DBACK TEST POOP 0"); button.clicked.connect(onClicked); if (HMD.active) { print("DBACK TEST POOP 1"); var walletReady = 3; if (Wallet.walletStatus === walletReady) { print("DBACK TEST POOP 2"); TABLET.gotoWebScreen(APP_URL); } else { print("DBACK TEST POOP 3"); TABLET.pushOntoStack(APP_URL); TABLET.loadQMLSource(MARKETPLACE_WALLET_QML_PATH); } } else { // Load the checkout wear tutorial for desktop users: Messages.sendLocalMessage('com.highfidelity.wear.tutorialChannel', 'checkoutEnter'); } }); _this.enterEntity = (function (entityID) { replicaList = []; collectZoneData(); if (ENTER_ZONE_SOUND.downloaded) { Audio.playSound(ENTER_ZONE_SOUND, { position: MyAvatar.position, volume: SHARED.AUDIO_VOLUME_LEVEL, localOnly: true }); } Entities.callEntityMethod(recycleBinID, 'enterCheckout'); Entities.callEntityMethod(scannerZone, 'enterCheckout'); setupApp(); isInZone = true; var avatarChildEntities = []; avatarChildEntities = SHARED.getAvatarChildEntities(MyAvatar); var left = true; var middle = false; avatarChildEntities.forEach(function (entityID) { if (replicaList.length < MAX_ITEMS){ var childUserData = Entities.getEntityProperties(entityID, 'userData').userData; var isAttachment = childUserData.indexOf("attached\":true"); var marketplaceID = Entities.getEntityProperties(entityID, 'marketplaceID').marketplaceID; if (marketplaceID && (isAttachment !== -1)) { spawnOverlayReplica(entityID); if (mini) { xOffset = 0; yOffset = 0.06; } else { xOffset = 0.005; yOffset = 0.1; } if (left) { spawnX -= itemHeight + xOffset; spawnZ -= itemHeight; left = false; middle = true; } else if (middle){ spawnX -= itemHeight + xOffset; spawnZ -= itemHeight; middle = false; } else { spawnY += yOffset; spawnX += itemHeight; spawnX += itemHeight; spawnZ += itemHeight; spawnZ += itemHeight; left = true; } } } }); var tabletTransform = { parentID: tableID, localPosition: tabletLocalOffset, localRotation: Quat.fromVec3Degrees(TABLET_ROTATIONAL_OFFSET) }; Overlays.editOverlay(HMD.tabletID, tabletTransform); var tabletTransformInterval = Script.setInterval(function() { Overlays.editOverlay(HMD.tabletID, tabletTransform); }, TRANSLATION_CHECK_INTERVAL); Script.setTimeout(function() { tabletTransformInterval.stop(); }, SHORTER_STOP_TIMEOUT); }); _this.leaveEntity = function() { Entities.callEntityMethod(recycleBinID, 'exitCheckout'); Entities.callEntityMethod(scannerZone, 'exitCheckout'); isInZone = false; var SCANNER_RANGE_METERS = 1000; Entities.findEntities(MyAvatar.position, SCANNER_RANGE_METERS).forEach(function(entity) { try { var name = Entities.getEntityProperties(entity).name; if (name.indexOf("Checkout Item") !== -1) { Entities.deleteEntity(entity); } } catch (e) { print("Error cleaning up."); } }); replicaList.forEach(function (overlayItem) { Overlays.deleteOverlay(overlayItem); }); replicaList = []; replicaStoredTransforms = {}; TABLET.removeButton(button); TABLET.gotoHomeScreen(); Overlays.editOverlay(HMD.tabletID, {parentID: MyAvatar.sessionUUID}); HMD.closeTablet(); }; _this.unload = function() { // make sure you leave the entity if you're still in there if (isInZone) { _this.leaveEntity(); } }; });