From 6763ca9b27bc073904d1767dcce0c217fcb8bc7e Mon Sep 17 00:00:00 2001 From: Rob Kayson Date: Mon, 3 Apr 2017 11:55:47 -0700 Subject: [PATCH 1/4] added tetherball create and entity scripts --- scripts/tutorials/createTetherballStick.js | 73 ++++ .../entity_scripts/tetherballStick.js | 411 ++++++++++++++++++ 2 files changed, 484 insertions(+) create mode 100644 scripts/tutorials/createTetherballStick.js create mode 100644 scripts/tutorials/entity_scripts/tetherballStick.js diff --git a/scripts/tutorials/createTetherballStick.js b/scripts/tutorials/createTetherballStick.js new file mode 100644 index 0000000000..a975c3a247 --- /dev/null +++ b/scripts/tutorials/createTetherballStick.js @@ -0,0 +1,73 @@ +"use strict"; +/* jslint vars: true, plusplus: true, forin: true*/ +/* globals Tablet, Script, AvatarList, Users, Entities, MyAvatar, Camera, Overlays, Vec3, Quat, Controller, print, getControllerWorldLocation */ +/* eslint indent: ["error", 4, { "outerIIFEBody": 0 }] */ +// +// createTetherballStick.js +// +// Created by Triplelexx on 17/03/04 +// Updated by MrRoboman on 17/03/26 +// Copyright 2017 High Fidelity, Inc. +// +// Creates an equippable stick with a tethered ball +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html + +var STICK_SCRIPT_URL = Script.resolvePath("./entity_scripts/tetherballStick.js?v=" + Date.now()); +var STICK_MODEL_URL = "http://hifi-content.s3.amazonaws.com/caitlyn/production/raveStick/newRaveStick2.fbx"; + +var avatarOrientation = MyAvatar.orientation; +avatarOrientation = Quat.safeEulerAngles(avatarOrientation); +avatarOrientation.x = 0; +avatarOrientation = Quat.fromVec3Degrees(avatarOrientation); +var startPosition = Vec3.sum(MyAvatar.getRightPalmPosition(), Vec3.multiply(1, Quat.getFront(avatarOrientation))); + +var STICK_PROPERTIES = { + type: 'Model', + name: "TetherballStick Stick", + modelURL: STICK_MODEL_URL, + position: startPosition, + rotation: MyAvatar.orientation, + dimensions: { + x: 0.0651, + y: 0.0651, + z: 0.5270 + }, + script: STICK_SCRIPT_URL, + color: { + red: 200, + green: 0, + blue: 20 + }, + shapeType: 'box', + lifetime: 3600, + userData: JSON.stringify({ + grabbableKey: { + grabbable: true, + spatialKey: { + rightRelativePosition: { + x: 0.05, + y: 0, + z: 0 + }, + leftRelativePosition: { + x: -0.05, + y: 0, + z: 0 + }, + relativeRotation: { + x: 0.4999999701976776, + y: 0.4999999701976776, + z: -0.4999999701976776, + w: 0.4999999701976776 + } + }, + invertSolidWhileHeld: true + }, + ownerID: MyAvatar.sessionUUID + }) +}; + +Entities.addEntity(STICK_PROPERTIES); +Script.stop(); diff --git a/scripts/tutorials/entity_scripts/tetherballStick.js b/scripts/tutorials/entity_scripts/tetherballStick.js new file mode 100644 index 0000000000..6fb249dc28 --- /dev/null +++ b/scripts/tutorials/entity_scripts/tetherballStick.js @@ -0,0 +1,411 @@ +"use strict"; +/* jslint vars: true, plusplus: true, forin: true*/ +/* globals Tablet, Script, AvatarList, Users, Entities, MyAvatar, Camera, Overlays, Vec3, Quat, Controller, print, getControllerWorldLocation */ +/* eslint indent: ["error", 4, { "outerIIFEBody": 0 }] */ +// +// tetherballStick.js +// +// Created by Triplelexx on 17/03/04 +// Updated by MrRoboman on 17/03/26 +// Copyright 2017 High Fidelity, Inc. +// +// Entity script for an equippable stick with a tethered ball +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html + +(function() { + var _this; + + var NULL_UUID = "{00000000-0000-0000-0000-000000000000}"; + var ENTITY_CHECK_INTERVAL = 5; // time in sec + var LINE_WIDTH = 0.02; + var BALL_SIZE = 0.175; + var BALL_DAMPING = 0.5; + var BALL_ANGULAR_DAMPING = 0.5; + var BALL_RESTITUTION = 0.4; + var BALL_DENSITY = 1000; + var MAX_DISTANCE_MULTIPLIER = 2; + var ACTION_DISTANCE = 0.25; + var ACTION_TIMESCALE = 0.025; + var ACTION_TAG = "TetherballStick Action"; + var BALL_NAME = "TetherballStick Ball"; + var LINE_NAME = "TetherballStick Line"; + var COLLISION_SOUND_URL = "http://public.highfidelity.io/sounds/Footsteps/FootstepW3Left-12db.wav"; + var INTERACT_SOUND_URL = "http://hifi-public.s3.amazonaws.com/sounds/color_busters/powerup.wav"; + var INTERACT_SOUND_VOLUME = 0.2; + var USE_INTERACT_SOUND = false; + var AVATAR_CHECK_RANGE = 5; // in meters + var TIP_OFFSET = 0.26; + var LIFETIME = 3600; + + tetherballStick = function() { + _this = this; + return; + }; + + tetherballStick.prototype = { + lastCheckForEntities: ENTITY_CHECK_INTERVAL, + originalDimensions: null, + ownerID: NULL_UUID, + userID: NULL_UUID, + ballID: NULL_UUID, + lineID: NULL_UUID, + actionID: NULL_UUID, + INTERACT_SOUND: undefined, + + preload: function(entityID) { + this.entityID = entityID; + if (USE_INTERACT_SOUND) { + this.INTERACT_SOUND = SoundCache.getSound(INTERACT_SOUND_URL); + } + this.originalDimensions = Entities.getEntityProperties(this.entityID).dimensions; + Script.update.connect(this.update); + }, + + unload: function() { + Script.update.disconnect(this.update); + }, + + update: function(dt) { + // _this during update due to loss of scope + if (_this.lastCheckForEntities >= ENTITY_CHECK_INTERVAL) { + _this.lastCheckForEntities = 0; + // everyone will be running this update loop + // the only action is to increment the timer till ENTITY_CHECK_INTERVAL + // when that is reached the userdata ownerID is simply validated by everyone + // if it's invalid call setNewOwner, to set a new valid user + if (!_this.checkForOwner()) { + return; + } + + // only one person should ever be running this far + if (_this.ownerID == MyAvatar.sessionUUID) { + _this.checkForEntities(); + } + } else { + _this.lastCheckForEntities += dt; + } + + _this.drawLine(); + }, + + getScale: function() { + var stickProps = Entities.getEntityProperties(this.entityID); + return stickProps.dimensions.z / this.originalDimensions.z; + }, + + checkForOwner: function() { + var stickProps = Entities.getEntityProperties(this.entityID); + try { + var stickData = JSON.parse(stickProps.userData); + var owner = AvatarManager.getAvatar(stickData.ownerID); + if (owner.sessionUUID !== undefined) { + this.ownerID = owner.sessionUUID; + return true; + } else { + // UUID is invalid + this.setNewOwner(); + return false; + } + } catch (e) { + // all other errors + this.setNewOwner(); + return false; + } + }, + + setNewOwner: function() { + var stickProps = Entities.getEntityProperties(this.entityID); + // I only want the closest client to be in charge of creating objects. + // the AvatarList also contains a null representing MyAvatar, + // a new array is created to start with containing the proper UUID + var avatarList = AvatarList.getAvatarIdentifiers() + .filter(Boolean) // remove the null + .concat(MyAvatar.sessionUUID); // add the ID + + var closestAvatarID = undefined; + var closestAvatarDistance = AVATAR_CHECK_RANGE; + avatarList.forEach(function(avatarID) { + var avatar = AvatarList.getAvatar(avatarID); + var distFrom = Vec3.distance(avatar.position, stickProps.position); + if (distFrom < closestAvatarDistance) { + closestAvatarDistance = distFrom; + closestAvatarID = avatarID; + } + }); + + // add reference to userData + try { + var stickData = JSON.parse(stickProps.userData); + stickData.ownerID = closestAvatarID; //NOTE: this will assign undefined if all avatars are further than AVATAR_CHECK_RANGE + Entities.editEntity(this.entityID, { + userData: JSON.stringify(stickData) + }); + } catch (e) { + } + }, + + checkForEntities: function() { + if (!this.hasBall()) { + this.createBall(); + } + if (!this.hasAction()) { + this.createAction(); + } + }, + + playInteractionSound: function() { + var stickProps = Entities.getEntityProperties(this.entityID); + var INTERACT_SOUND_OPTIONS = { + volume: INTERACT_SOUND_VOLUME, + loop: false, + position: stickProps.position + }; + Audio.playSound(this.INTERACT_SOUND, INTERACT_SOUND_OPTIONS); + }, + + getTipPosition: function() { + var stickProps = Entities.getEntityProperties(this.entityID); + + var scale = this.getScale(); + var frontVec = Quat.getFront(stickProps.rotation); + var frontOffset = Vec3.multiply(frontVec, TIP_OFFSET * scale); + var tipPosition = Vec3.sum(stickProps.position, frontOffset); + + return tipPosition; + }, + + getStickFrontPosition: function() { + var stickProps = Entities.getEntityProperties(this.entityID); + var stickFront = Quat.getFront(stickProps.rotation); + var tipPosition = this.getTipPosition(); + return Vec3.sum(tipPosition, Vec3.multiply(TIP_OFFSET*.4, stickFront)); + }, + + capBallDistance: function() { + var stickProps = Entities.getEntityProperties(this.entityID); + var ballProps = Entities.getEntityProperties(this.ballID); + var tipPosition = this.getTipPosition(); + var scale = this.getScale(); + var distance = Vec3.distance(tipPosition, ballProps.position); + var maxDistance = ACTION_DISTANCE * MAX_DISTANCE_MULTIPLIER * scale; + + if(distance > maxDistance) { + var direction = Vec3.normalize(Vec3.subtract(ballProps.position, tipPosition)); + var newPosition = Vec3.sum(tipPosition, Vec3.multiply(maxDistance, direction)); + Entities.editEntity(this.ballID, { + position: newPosition + }) + } + }, + + startNearGrab: function(id, params) { + var stickProps = Entities.getEntityProperties(this.entityID); + // set variables from data in case someone else created the components + try { + var stickData = JSON.parse(stickProps.userData); + this.userID = MyAvatar.sessionUUID; + this.ballID = stickData.ballID; + this.actionID = stickData.actionID; + if(!this.hasLine()) { + this.createLine(); + } + if (USE_INTERACT_SOUND) { + var hand = params[0]; + Controller.triggerShortHapticPulse(1, hand); + this.playInteractionSound(); + } + } catch (e) { } + }, + + releaseGrab: function(id, params) { + this.userID = NULL_UUID; + if (USE_INTERACT_SOUND) { + this.playInteractionSound(); + } + }, + + continueNearGrab: function(id, params) { + this.repositionAction(); + this.updateDimensions(); + this.capBallDistance(); + }, + + createLine: function() { + var stickProps = Entities.getEntityProperties(this.entityID); + this.lineID = Entities.addEntity({ + type: "PolyLine", + name: LINE_NAME, + color: { + red: 0, + green: 120, + blue: 250 + }, + textures: "https://hifi-public.s3.amazonaws.com/alan/Particles/Particle-Sprite-Smoke-1.png", + position: stickProps.position, + dimensions: { + x: 10, + y: 10, + z: 10 + }, + lifetime: LIFETIME + }); + }, + + deleteLine: function() { + Entities.deleteEntity(this.lineID); + this.lineID = NULL_UUID; + }, + + hasLine: function() { + var lineProps = Entities.getEntityProperties(this.lineID); + return lineProps.name == LINE_NAME; + }, + + createBall: function() { + var stickProps = Entities.getEntityProperties(this.entityID); + + this.ballID = Entities.addEntity({ + type: "Model", + modelURL: "http://hifi-content.s3.amazonaws.com/Examples%20Content/production/marblecollection/Star.fbx", + name: BALL_NAME, + shapeType: "Sphere", + position: this.getStickFrontPosition(), + lifetime: LIFETIME, + collisionSoundURL: COLLISION_SOUND_URL, + dimensions: { + x: BALL_SIZE, + y: BALL_SIZE, + z: BALL_SIZE + }, + gravity: { + x: 0.0, + y: -9.8, + z: 0.0 + }, + damping: BALL_DAMPING, + angularDamping: BALL_ANGULAR_DAMPING, + density: BALL_DENSITY, + restitution: BALL_RESTITUTION, + dynamic: true, + collidesWith: "static,dynamic,otherAvatar,", + userData: JSON.stringify({ + grabbableKey: { + grabbable: false + } + }) + }); + + // add reference to userData + try { + var stickData = JSON.parse(stickProps.userData); + stickData.ballID = this.ballID; + Entities.editEntity(this.entityID, { + userData: JSON.stringify(stickData) + }); + } catch (e) {} + }, + + hasBall: function() { + // validate the userData to handle unexpected item deletion + var stickProps = Entities.getEntityProperties(this.entityID); + try { + var data = JSON.parse(stickProps.userData); + var ballProps = Entities.getEntityProperties(data.ballID); + return ballProps.name == BALL_NAME; + } catch (e) { + return false; + } + }, + + createAction: function() { + var stickProps = Entities.getEntityProperties(this.entityID); + this.actionID = Entities.addAction("offset", this.ballID, { + pointToOffsetFrom: stickProps.position, + linearDistance: ACTION_DISTANCE, + tag: ACTION_TAG, + linearTimeScale: ACTION_TIMESCALE + }); + + // add reference to userData + try { + var stickData = JSON.parse(stickProps.userData); + stickData.actionID = this.actionID; + Entities.editEntity(this.entityID, { + userData: JSON.stringify(stickData) + }); + } catch (e) {} + }, + + hasAction: function() { + // validate the userData to handle unexpected item deletion + var stickProps = Entities.getEntityProperties(this.entityID); + try { + var stickData = JSON.parse(stickProps.userData); + var actionProps = Entities.getActionArguments(stickData.ballID, stickData.actionID); + return actionProps.tag == ACTION_TAG; + } catch (e) { + return false; + } + }, + + hasRequiredComponents: function() { + return this.hasBall() && this.hasAction() && this.hasLine(); + }, + + updateDimensions: function() { + var scale = this.getScale(); + Entities.editEntity(this.ballID, { + dimensions: { + x: BALL_SIZE * scale, + y: BALL_SIZE * scale, + z: BALL_SIZE * scale + } + }); + }, + + drawLine: function() { + if(!this.hasLine()) + return; + + var stickProps = Entities.getEntityProperties(this.entityID); + var tipPosition = this.getTipPosition(); + var ballProps = Entities.getEntityProperties(this.ballID); + var cameraQuat = Vec3.multiplyQbyV(Camera.getOrientation(), Vec3.UNIT_NEG_Z); + var linePoints = []; + var normals = []; + var strokeWidths = []; + linePoints.push(Vec3.ZERO); + normals.push(cameraQuat); + strokeWidths.push(LINE_WIDTH); + linePoints.push(Vec3.subtract(ballProps.position, tipPosition)); + normals.push(cameraQuat); + strokeWidths.push(LINE_WIDTH); + + var lineProps = Entities.getEntityProperties(this.lineID); + Entities.editEntity(this.lineID, { + linePoints: linePoints, + normals: normals, + strokeWidths: strokeWidths, + position: tipPosition, + }); + }, + + repositionAction: function() { + var stickProps = Entities.getEntityProperties(this.entityID); + var tipPosition = this.getTipPosition(); + var scale = this.getScale(); + + Entities.updateAction(this.ballID, this.actionID, { + pointToOffsetFrom: tipPosition, + linearDistance: ACTION_DISTANCE * scale, + tag: ACTION_TAG, + linearTimeScale: ACTION_TIMESCALE + }); + } + }; + + // entity scripts should return a newly constructed object of our type + return new tetherballStick(); +}); From 49858d3ec5c84f281976290fe4aa3d565e65b5c0 Mon Sep 17 00:00:00 2001 From: Rob Kayson Date: Fri, 7 Apr 2017 19:16:29 -0700 Subject: [PATCH 2/4] Equip stick instead of grab. String is less rigid. Fixed bug where the ball was not recognized when other clients equipped the stick. --- scripts/tutorials/createTetherballStick.js | 98 +++-- .../entity_scripts/tetherballStick.js | 407 ++++++------------ 2 files changed, 194 insertions(+), 311 deletions(-) diff --git a/scripts/tutorials/createTetherballStick.js b/scripts/tutorials/createTetherballStick.js index a975c3a247..d0c069d186 100644 --- a/scripts/tutorials/createTetherballStick.js +++ b/scripts/tutorials/createTetherballStick.js @@ -14,20 +14,61 @@ // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +var NULL_UUID = "{00000000-0000-0000-0000-000000000000}"; +var LIFETIME = 3600; +var BALL_SIZE = 0.175; +var BALL_DAMPING = 0.5; +var BALL_ANGULAR_DAMPING = 0.5; +var BALL_RESTITUTION = 0.4; +var BALL_DENSITY = 1000; var STICK_SCRIPT_URL = Script.resolvePath("./entity_scripts/tetherballStick.js?v=" + Date.now()); var STICK_MODEL_URL = "http://hifi-content.s3.amazonaws.com/caitlyn/production/raveStick/newRaveStick2.fbx"; +var COLLISION_SOUND_URL = "http://public.highfidelity.io/sounds/Footsteps/FootstepW3Left-12db.wav"; var avatarOrientation = MyAvatar.orientation; avatarOrientation = Quat.safeEulerAngles(avatarOrientation); avatarOrientation.x = 0; avatarOrientation = Quat.fromVec3Degrees(avatarOrientation); -var startPosition = Vec3.sum(MyAvatar.getRightPalmPosition(), Vec3.multiply(1, Quat.getFront(avatarOrientation))); +var front = Quat.getFront(avatarOrientation); +var stickStartPosition = Vec3.sum(MyAvatar.getRightPalmPosition(), front); +var ballStartPosition = Vec3.sum(stickStartPosition, Vec3.multiply(0.36, front)); + +var ballID = Entities.addEntity({ + type: "Model", + modelURL: "http://hifi-content.s3.amazonaws.com/Examples%20Content/production/marblecollection/Star.fbx", + name: "TetherballStick Ball", + shapeType: "Sphere", + position: ballStartPosition, + lifetime: LIFETIME, + collisionSoundURL: COLLISION_SOUND_URL, + dimensions: { + x: BALL_SIZE, + y: BALL_SIZE, + z: BALL_SIZE + }, + gravity: { + x: 0.0, + y: -9.8, + z: 0.0 + }, + damping: BALL_DAMPING, + angularDamping: BALL_ANGULAR_DAMPING, + density: BALL_DENSITY, + restitution: BALL_RESTITUTION, + dynamic: true, + collidesWith: "static,dynamic,otherAvatar,", + userData: JSON.stringify({ + grabbableKey: { + grabbable: false + } + }) +}); var STICK_PROPERTIES = { type: 'Model', name: "TetherballStick Stick", modelURL: STICK_MODEL_URL, - position: startPosition, + position: stickStartPosition, rotation: MyAvatar.orientation, dimensions: { x: 0.0651, @@ -41,31 +82,40 @@ var STICK_PROPERTIES = { blue: 20 }, shapeType: 'box', - lifetime: 3600, + lifetime: LIFETIME, userData: JSON.stringify({ grabbableKey: { - grabbable: true, - spatialKey: { - rightRelativePosition: { - x: 0.05, - y: 0, - z: 0 - }, - leftRelativePosition: { - x: -0.05, - y: 0, - z: 0 - }, - relativeRotation: { - x: 0.4999999701976776, - y: 0.4999999701976776, - z: -0.4999999701976776, - w: 0.4999999701976776 - } - }, - invertSolidWhileHeld: true + invertSolidWhileHeld: true, + ignoreIK: false }, - ownerID: MyAvatar.sessionUUID + wearable: { + joints: { + RightHand: [{ + x: 0.15539926290512085, + y: 0.14493153989315033, + z: 0.023641478270292282 + }, { + x: 0.5481458902359009, + y: -0.4470711946487427, + z: -0.3148134648799896, + w: 0.6328644752502441 + }], + LeftHand: [{ + x: -0.14998853206634521, + y: 0.17033983767032623, + z: 0.023199155926704407 + }, + { + x: 0.6623835563659668, + y: -0.1671387255191803, + z: 0.7071226835250854, + w: 0.1823924481868744 + }] + } + }, + ownerID: MyAvatar.sessionUUID, + ballID: ballID, + lifetime: LIFETIME }) }; diff --git a/scripts/tutorials/entity_scripts/tetherballStick.js b/scripts/tutorials/entity_scripts/tetherballStick.js index 6fb249dc28..35e704b709 100644 --- a/scripts/tutorials/entity_scripts/tetherballStick.js +++ b/scripts/tutorials/entity_scripts/tetherballStick.js @@ -18,178 +18,107 @@ var _this; var NULL_UUID = "{00000000-0000-0000-0000-000000000000}"; - var ENTITY_CHECK_INTERVAL = 5; // time in sec var LINE_WIDTH = 0.02; - var BALL_SIZE = 0.175; - var BALL_DAMPING = 0.5; - var BALL_ANGULAR_DAMPING = 0.5; - var BALL_RESTITUTION = 0.4; - var BALL_DENSITY = 1000; - var MAX_DISTANCE_MULTIPLIER = 2; - var ACTION_DISTANCE = 0.25; - var ACTION_TIMESCALE = 0.025; - var ACTION_TAG = "TetherballStick Action"; - var BALL_NAME = "TetherballStick Ball"; - var LINE_NAME = "TetherballStick Line"; + var MAX_DISTANCE_MULTIPLIER = 4; + var ACTION_DISTANCE = 0.35; + var ACTION_TIMESCALE = 0.035; var COLLISION_SOUND_URL = "http://public.highfidelity.io/sounds/Footsteps/FootstepW3Left-12db.wav"; - var INTERACT_SOUND_URL = "http://hifi-public.s3.amazonaws.com/sounds/color_busters/powerup.wav"; - var INTERACT_SOUND_VOLUME = 0.2; - var USE_INTERACT_SOUND = false; - var AVATAR_CHECK_RANGE = 5; // in meters var TIP_OFFSET = 0.26; var LIFETIME = 3600; tetherballStick = function() { _this = this; - return; }; tetherballStick.prototype = { - lastCheckForEntities: ENTITY_CHECK_INTERVAL, - originalDimensions: null, - ownerID: NULL_UUID, - userID: NULL_UUID, + entityID: NULL_UUID, ballID: NULL_UUID, lineID: NULL_UUID, - actionID: NULL_UUID, - INTERACT_SOUND: undefined, + + getUserData: function(key) { + try { + var stickProps = Entities.getEntityProperties(this.entityID); + var userData = JSON.parse(stickProps.userData); + return userData[key]; + } catch (e) { + print("Error parsing Tetherball Stick UserData in file " + + e.fileName + " on line " + e.lineNumber); + } + }, preload: function(entityID) { this.entityID = entityID; - if (USE_INTERACT_SOUND) { - this.INTERACT_SOUND = SoundCache.getSound(INTERACT_SOUND_URL); - } - this.originalDimensions = Entities.getEntityProperties(this.entityID).dimensions; - Script.update.connect(this.update); - }, - - unload: function() { - Script.update.disconnect(this.update); + this.ballID = this.getUserData("ballID"); }, update: function(dt) { - // _this during update due to loss of scope - if (_this.lastCheckForEntities >= ENTITY_CHECK_INTERVAL) { - _this.lastCheckForEntities = 0; - // everyone will be running this update loop - // the only action is to increment the timer till ENTITY_CHECK_INTERVAL - // when that is reached the userdata ownerID is simply validated by everyone - // if it's invalid call setNewOwner, to set a new valid user - if (!_this.checkForOwner()) { - return; - } + _this.drawLine(); + }, - // only one person should ever be running this far - if (_this.ownerID == MyAvatar.sessionUUID) { - _this.checkForEntities(); - } + startEquip: function(id, params) { + this.removeActions(); + this.createLine(); + + Script.update.connect(this.update); + }, + + continueEquip: function(id, params) { + var stickProps = Entities.getEntityProperties(this.entityID); + var ballProps = Entities.getEntityProperties(this.ballID); + var tipPosition = this.getTipPosition(); + var distance = Vec3.distance(tipPosition, ballProps.position); + var maxDistance = ACTION_DISTANCE; + + var dVel = Vec3.subtract(ballProps.velocity, stickProps.velocity); + var dPos = Vec3.subtract(ballProps.position, stickProps.position); + var ballWithinMaxDistance = distance <= maxDistance; + var ballMovingCloserToStick = Vec3.dot(dVel, dPos) < 0; + var ballAboveStick = ballProps.position.y > tipPosition.y; + + if(this.hasAction()) { + if(ballWithinMaxDistance && (ballMovingCloserToStick || ballAboveStick)) { + this.removeActions(); } else { - _this.lastCheckForEntities += dt; + this.updateOffsetAction(); } + } else if(!ballWithinMaxDistance && !ballMovingCloserToStick){ + this.createOffsetAction(); + } - _this.drawLine(); + this.capBallDistance(); }, - getScale: function() { - var stickProps = Entities.getEntityProperties(this.entityID); - return stickProps.dimensions.z / this.originalDimensions.z; - }, + releaseEquip: function(id, params) { + this.deleteLine(); + this.createSpringAction(); - checkForOwner: function() { - var stickProps = Entities.getEntityProperties(this.entityID); - try { - var stickData = JSON.parse(stickProps.userData); - var owner = AvatarManager.getAvatar(stickData.ownerID); - if (owner.sessionUUID !== undefined) { - this.ownerID = owner.sessionUUID; - return true; - } else { - // UUID is invalid - this.setNewOwner(); - return false; - } - } catch (e) { - // all other errors - this.setNewOwner(); - return false; - } - }, - - setNewOwner: function() { - var stickProps = Entities.getEntityProperties(this.entityID); - // I only want the closest client to be in charge of creating objects. - // the AvatarList also contains a null representing MyAvatar, - // a new array is created to start with containing the proper UUID - var avatarList = AvatarList.getAvatarIdentifiers() - .filter(Boolean) // remove the null - .concat(MyAvatar.sessionUUID); // add the ID - - var closestAvatarID = undefined; - var closestAvatarDistance = AVATAR_CHECK_RANGE; - avatarList.forEach(function(avatarID) { - var avatar = AvatarList.getAvatar(avatarID); - var distFrom = Vec3.distance(avatar.position, stickProps.position); - if (distFrom < closestAvatarDistance) { - closestAvatarDistance = distFrom; - closestAvatarID = avatarID; - } - }); - - // add reference to userData - try { - var stickData = JSON.parse(stickProps.userData); - stickData.ownerID = closestAvatarID; //NOTE: this will assign undefined if all avatars are further than AVATAR_CHECK_RANGE - Entities.editEntity(this.entityID, { - userData: JSON.stringify(stickData) - }); - } catch (e) { - } - }, - - checkForEntities: function() { - if (!this.hasBall()) { - this.createBall(); - } - if (!this.hasAction()) { - this.createAction(); - } - }, - - playInteractionSound: function() { - var stickProps = Entities.getEntityProperties(this.entityID); - var INTERACT_SOUND_OPTIONS = { - volume: INTERACT_SOUND_VOLUME, - loop: false, - position: stickProps.position - }; - Audio.playSound(this.INTERACT_SOUND, INTERACT_SOUND_OPTIONS); + Script.update.disconnect(this.update); }, getTipPosition: function() { - var stickProps = Entities.getEntityProperties(this.entityID); + var stickProps = Entities.getEntityProperties(this.entityID); + var stickFront = Quat.getFront(stickProps.rotation); + var frontOffset = Vec3.multiply(stickFront, TIP_OFFSET); + var tipPosition = Vec3.sum(stickProps.position, frontOffset); - var scale = this.getScale(); - var frontVec = Quat.getFront(stickProps.rotation); - var frontOffset = Vec3.multiply(frontVec, TIP_OFFSET * scale); - var tipPosition = Vec3.sum(stickProps.position, frontOffset); - - return tipPosition; + return tipPosition; }, getStickFrontPosition: function() { - var stickProps = Entities.getEntityProperties(this.entityID); - var stickFront = Quat.getFront(stickProps.rotation); - var tipPosition = this.getTipPosition(); - return Vec3.sum(tipPosition, Vec3.multiply(TIP_OFFSET*.4, stickFront)); + var stickProps = Entities.getEntityProperties(this.entityID); + var stickFront = Quat.getFront(stickProps.rotation); + var tipPosition = this.getTipPosition(); + var frontPostion = Vec3.sum(tipPosition, Vec3.multiply(TIP_OFFSET * 0.4, stickFront)); + + return frontPostion; }, capBallDistance: function() { var stickProps = Entities.getEntityProperties(this.entityID); var ballProps = Entities.getEntityProperties(this.ballID); var tipPosition = this.getTipPosition(); - var scale = this.getScale(); var distance = Vec3.distance(tipPosition, ballProps.position); - var maxDistance = ACTION_DISTANCE * MAX_DISTANCE_MULTIPLIER * scale; + var maxDistance = ACTION_DISTANCE * MAX_DISTANCE_MULTIPLIER; if(distance > maxDistance) { var direction = Vec3.normalize(Vec3.subtract(ballProps.position, tipPosition)); @@ -200,175 +129,39 @@ } }, - startNearGrab: function(id, params) { - var stickProps = Entities.getEntityProperties(this.entityID); - // set variables from data in case someone else created the components - try { - var stickData = JSON.parse(stickProps.userData); - this.userID = MyAvatar.sessionUUID; - this.ballID = stickData.ballID; - this.actionID = stickData.actionID; - if(!this.hasLine()) { - this.createLine(); - } - if (USE_INTERACT_SOUND) { - var hand = params[0]; - Controller.triggerShortHapticPulse(1, hand); - this.playInteractionSound(); - } - } catch (e) { } - }, - - releaseGrab: function(id, params) { - this.userID = NULL_UUID; - if (USE_INTERACT_SOUND) { - this.playInteractionSound(); - } - }, - - continueNearGrab: function(id, params) { - this.repositionAction(); - this.updateDimensions(); - this.capBallDistance(); - }, - createLine: function() { - var stickProps = Entities.getEntityProperties(this.entityID); + if(!this.hasLine()) { this.lineID = Entities.addEntity({ type: "PolyLine", - name: LINE_NAME, + name: "TetherballStick Line", color: { red: 0, green: 120, blue: 250 }, textures: "https://hifi-public.s3.amazonaws.com/alan/Particles/Particle-Sprite-Smoke-1.png", - position: stickProps.position, + position: this.getTipPosition(), dimensions: { x: 10, y: 10, z: 10 }, - lifetime: LIFETIME + lifetime: this.getUserData("lifetime") }); + } }, deleteLine: function() { - Entities.deleteEntity(this.lineID); - this.lineID = NULL_UUID; + Entities.deleteEntity(this.lineID); + this.lineID = NULL_UUID; }, hasLine: function() { - var lineProps = Entities.getEntityProperties(this.lineID); - return lineProps.name == LINE_NAME; - }, - - createBall: function() { - var stickProps = Entities.getEntityProperties(this.entityID); - - this.ballID = Entities.addEntity({ - type: "Model", - modelURL: "http://hifi-content.s3.amazonaws.com/Examples%20Content/production/marblecollection/Star.fbx", - name: BALL_NAME, - shapeType: "Sphere", - position: this.getStickFrontPosition(), - lifetime: LIFETIME, - collisionSoundURL: COLLISION_SOUND_URL, - dimensions: { - x: BALL_SIZE, - y: BALL_SIZE, - z: BALL_SIZE - }, - gravity: { - x: 0.0, - y: -9.8, - z: 0.0 - }, - damping: BALL_DAMPING, - angularDamping: BALL_ANGULAR_DAMPING, - density: BALL_DENSITY, - restitution: BALL_RESTITUTION, - dynamic: true, - collidesWith: "static,dynamic,otherAvatar,", - userData: JSON.stringify({ - grabbableKey: { - grabbable: false - } - }) - }); - - // add reference to userData - try { - var stickData = JSON.parse(stickProps.userData); - stickData.ballID = this.ballID; - Entities.editEntity(this.entityID, { - userData: JSON.stringify(stickData) - }); - } catch (e) {} - }, - - hasBall: function() { - // validate the userData to handle unexpected item deletion - var stickProps = Entities.getEntityProperties(this.entityID); - try { - var data = JSON.parse(stickProps.userData); - var ballProps = Entities.getEntityProperties(data.ballID); - return ballProps.name == BALL_NAME; - } catch (e) { - return false; - } - }, - - createAction: function() { - var stickProps = Entities.getEntityProperties(this.entityID); - this.actionID = Entities.addAction("offset", this.ballID, { - pointToOffsetFrom: stickProps.position, - linearDistance: ACTION_DISTANCE, - tag: ACTION_TAG, - linearTimeScale: ACTION_TIMESCALE - }); - - // add reference to userData - try { - var stickData = JSON.parse(stickProps.userData); - stickData.actionID = this.actionID; - Entities.editEntity(this.entityID, { - userData: JSON.stringify(stickData) - }); - } catch (e) {} - }, - - hasAction: function() { - // validate the userData to handle unexpected item deletion - var stickProps = Entities.getEntityProperties(this.entityID); - try { - var stickData = JSON.parse(stickProps.userData); - var actionProps = Entities.getActionArguments(stickData.ballID, stickData.actionID); - return actionProps.tag == ACTION_TAG; - } catch (e) { - return false; - } - }, - - hasRequiredComponents: function() { - return this.hasBall() && this.hasAction() && this.hasLine(); - }, - - updateDimensions: function() { - var scale = this.getScale(); - Entities.editEntity(this.ballID, { - dimensions: { - x: BALL_SIZE * scale, - y: BALL_SIZE * scale, - z: BALL_SIZE * scale - } - }); + return this.lineID != NULL_UUID; }, drawLine: function() { - if(!this.hasLine()) - return; - + if(this.hasLine()) { var stickProps = Entities.getEntityProperties(this.entityID); var tipPosition = this.getTipPosition(); var ballProps = Entities.getEntityProperties(this.ballID); @@ -390,19 +183,59 @@ strokeWidths: strokeWidths, position: tipPosition, }); + } }, - repositionAction: function() { - var stickProps = Entities.getEntityProperties(this.entityID); - var tipPosition = this.getTipPosition(); - var scale = this.getScale(); + createOffsetAction: function() { + this.removeActions(); - Entities.updateAction(this.ballID, this.actionID, { - pointToOffsetFrom: tipPosition, - linearDistance: ACTION_DISTANCE * scale, - tag: ACTION_TAG, - linearTimeScale: ACTION_TIMESCALE + Entities.addAction("offset", this.ballID, { + pointToOffsetFrom: this.getTipPosition(), + linearDistance: ACTION_DISTANCE, + linearTimeScale: ACTION_TIMESCALE + }); + }, + + createSpringAction: function() { + this.removeActions(); + + Entities.addAction("spring", this.ballID, { + targetPosition: this.getTipPosition(), + linearTimeScale: ACTION_TIMESCALE + }); + }, + + updateOffsetAction: function() { + var actionIDs = Entities.getActionIDs(this.ballID); + var actionID; + + // Sometimes two offset actions are applied to the ball simultaneously. + // Here we ensure that only the most recent action is updated + // and the rest are deleted. + while(actionIDs.length > 1) { + actionID = actionIDs.shift(); + Entities.deleteAction(this.ballID, actionID); + } + + actionID = actionIDs.shift(); + if(actionID) { + Entities.updateAction(this.ballID, actionID, { + pointToOffsetFrom: this.getTipPosition() }); + } + }, + + removeActions: function() { + // Ball should only ever have one action, but sometimes sneaky little actions attach themselves + // So we remove all possible actions in this call. + var actionIDs = Entities.getActionIDs(this.ballID); + for(var i = 0; i < actionIDs.length; i++) { + Entities.deleteAction(this.ballID, actionIDs[i]); + } + }, + + hasAction: function() { + return Entities.getActionIDs(this.ballID).length > 0; } }; From 2257af42bee382c3262109d7ede0c1e95fc735ce Mon Sep 17 00:00:00 2001 From: Rob Kayson Date: Wed, 12 Apr 2017 17:07:59 -0700 Subject: [PATCH 3/4] removed extra actions, creating all entities in create script. --- scripts/tutorials/createTetherballStick.js | 32 ++- .../entity_scripts/tetherballStick.js | 199 ++++-------------- 2 files changed, 75 insertions(+), 156 deletions(-) diff --git a/scripts/tutorials/createTetherballStick.js b/scripts/tutorials/createTetherballStick.js index d0c069d186..8d36d8ee59 100644 --- a/scripts/tutorials/createTetherballStick.js +++ b/scripts/tutorials/createTetherballStick.js @@ -21,6 +21,9 @@ var BALL_DAMPING = 0.5; var BALL_ANGULAR_DAMPING = 0.5; var BALL_RESTITUTION = 0.4; var BALL_DENSITY = 1000; +var ACTION_DISTANCE = 0.35; +var ACTION_TIMESCALE = 0.035; +var MAX_DISTANCE_MULTIPLIER = 4; var STICK_SCRIPT_URL = Script.resolvePath("./entity_scripts/tetherballStick.js?v=" + Date.now()); var STICK_MODEL_URL = "http://hifi-content.s3.amazonaws.com/caitlyn/production/raveStick/newRaveStick2.fbx"; var COLLISION_SOUND_URL = "http://public.highfidelity.io/sounds/Footsteps/FootstepW3Left-12db.wav"; @@ -64,6 +67,30 @@ var ballID = Entities.addEntity({ }) }); +var lineID = Entities.addEntity({ + type: "PolyLine", + name: "TetherballStick Line", + color: { + red: 0, + green: 120, + blue: 250 + }, + textures: "https://hifi-public.s3.amazonaws.com/alan/Particles/Particle-Sprite-Smoke-1.png", + position: ballStartPosition, + dimensions: { + x: 10, + y: 10, + z: 10 + }, + lifetime: LIFETIME +}); + +var actionID = Entities.addAction("offset", ballID, { + pointToOffsetFrom: stickStartPosition, + linearDistance: ACTION_DISTANCE, + linearTimeScale: ACTION_TIMESCALE +}); + var STICK_PROPERTIES = { type: 'Model', name: "TetherballStick Stick", @@ -115,7 +142,10 @@ var STICK_PROPERTIES = { }, ownerID: MyAvatar.sessionUUID, ballID: ballID, - lifetime: LIFETIME + lineID: lineID, + actionID: actionID, + lifetime: LIFETIME, + maxDistanceBetweenBallAndStick: ACTION_DISTANCE * MAX_DISTANCE_MULTIPLIER }) }; diff --git a/scripts/tutorials/entity_scripts/tetherballStick.js b/scripts/tutorials/entity_scripts/tetherballStick.js index 35e704b709..9250675d50 100644 --- a/scripts/tutorials/entity_scripts/tetherballStick.js +++ b/scripts/tutorials/entity_scripts/tetherballStick.js @@ -17,29 +17,21 @@ (function() { var _this; - var NULL_UUID = "{00000000-0000-0000-0000-000000000000}"; var LINE_WIDTH = 0.02; - var MAX_DISTANCE_MULTIPLIER = 4; - var ACTION_DISTANCE = 0.35; - var ACTION_TIMESCALE = 0.035; var COLLISION_SOUND_URL = "http://public.highfidelity.io/sounds/Footsteps/FootstepW3Left-12db.wav"; var TIP_OFFSET = 0.26; - var LIFETIME = 3600; tetherballStick = function() { _this = this; }; tetherballStick.prototype = { - entityID: NULL_UUID, - ballID: NULL_UUID, - lineID: NULL_UUID, - getUserData: function(key) { + getUserData: function() { try { var stickProps = Entities.getEntityProperties(this.entityID); var userData = JSON.parse(stickProps.userData); - return userData[key]; + return userData; } catch (e) { print("Error parsing Tetherball Stick UserData in file " + e.fileName + " on line " + e.lineNumber); @@ -48,51 +40,36 @@ preload: function(entityID) { this.entityID = entityID; - this.ballID = this.getUserData("ballID"); + + var userData = this.getUserData(); + this.ballID = userData.ballID; + this.lineID = userData.lineID; + this.actionID = userData.actionID; + this.maxDistanceBetweenBallAndStick = userData.maxDistanceBetweenBallAndStick; + + // Script.update.connect(this.update); + }, + + unload: function() { + // Script.update.disconnect(this.update); }, update: function(dt) { _this.drawLine(); }, - startEquip: function(id, params) { - this.removeActions(); - this.createLine(); - - Script.update.connect(this.update); + startEquip: function() { + Script.update.disconnect(this.update); }, continueEquip: function(id, params) { - var stickProps = Entities.getEntityProperties(this.entityID); - var ballProps = Entities.getEntityProperties(this.ballID); - var tipPosition = this.getTipPosition(); - var distance = Vec3.distance(tipPosition, ballProps.position); - var maxDistance = ACTION_DISTANCE; - - var dVel = Vec3.subtract(ballProps.velocity, stickProps.velocity); - var dPos = Vec3.subtract(ballProps.position, stickProps.position); - var ballWithinMaxDistance = distance <= maxDistance; - var ballMovingCloserToStick = Vec3.dot(dVel, dPos) < 0; - var ballAboveStick = ballProps.position.y > tipPosition.y; - - if(this.hasAction()) { - if(ballWithinMaxDistance && (ballMovingCloserToStick || ballAboveStick)) { - this.removeActions(); - } else { - this.updateOffsetAction(); - } - } else if(!ballWithinMaxDistance && !ballMovingCloserToStick){ - this.createOffsetAction(); - } - + this.updateOffsetAction(); this.capBallDistance(); + this.drawLine(); }, - releaseEquip: function(id, params) { - this.deleteLine(); - this.createSpringAction(); - - Script.update.disconnect(this.update); + releaseEquip: function() { + Script.update.connect(this.update); }, getTipPosition: function() { @@ -104,13 +81,10 @@ return tipPosition; }, - getStickFrontPosition: function() { - var stickProps = Entities.getEntityProperties(this.entityID); - var stickFront = Quat.getFront(stickProps.rotation); - var tipPosition = this.getTipPosition(); - var frontPostion = Vec3.sum(tipPosition, Vec3.multiply(TIP_OFFSET * 0.4, stickFront)); - - return frontPostion; + updateOffsetAction: function() { + Entities.updateAction(this.ballID, this.actionID, { + pointToOffsetFrom: this.getTipPosition() + }); }, capBallDistance: function() { @@ -118,7 +92,7 @@ var ballProps = Entities.getEntityProperties(this.ballID); var tipPosition = this.getTipPosition(); var distance = Vec3.distance(tipPosition, ballProps.position); - var maxDistance = ACTION_DISTANCE * MAX_DISTANCE_MULTIPLIER; + var maxDistance = this.maxDistanceBetweenBallAndStick; if(distance > maxDistance) { var direction = Vec3.normalize(Vec3.subtract(ballProps.position, tipPosition)); @@ -129,113 +103,28 @@ } }, - createLine: function() { - if(!this.hasLine()) { - this.lineID = Entities.addEntity({ - type: "PolyLine", - name: "TetherballStick Line", - color: { - red: 0, - green: 120, - blue: 250 - }, - textures: "https://hifi-public.s3.amazonaws.com/alan/Particles/Particle-Sprite-Smoke-1.png", - position: this.getTipPosition(), - dimensions: { - x: 10, - y: 10, - z: 10 - }, - lifetime: this.getUserData("lifetime") - }); - } - }, - - deleteLine: function() { - Entities.deleteEntity(this.lineID); - this.lineID = NULL_UUID; - }, - - hasLine: function() { - return this.lineID != NULL_UUID; - }, - drawLine: function() { - if(this.hasLine()) { - var stickProps = Entities.getEntityProperties(this.entityID); - var tipPosition = this.getTipPosition(); - var ballProps = Entities.getEntityProperties(this.ballID); - var cameraQuat = Vec3.multiplyQbyV(Camera.getOrientation(), Vec3.UNIT_NEG_Z); - var linePoints = []; - var normals = []; - var strokeWidths = []; - linePoints.push(Vec3.ZERO); - normals.push(cameraQuat); - strokeWidths.push(LINE_WIDTH); - linePoints.push(Vec3.subtract(ballProps.position, tipPosition)); - normals.push(cameraQuat); - strokeWidths.push(LINE_WIDTH); + var stickProps = Entities.getEntityProperties(this.entityID); + var tipPosition = this.getTipPosition(); + var ballProps = Entities.getEntityProperties(this.ballID); + var cameraQuat = Vec3.multiplyQbyV(Camera.getOrientation(), Vec3.UNIT_NEG_Z); + var linePoints = []; + var normals = []; + var strokeWidths = []; + linePoints.push(Vec3.ZERO); + normals.push(cameraQuat); + strokeWidths.push(LINE_WIDTH); + linePoints.push(Vec3.subtract(ballProps.position, tipPosition)); + normals.push(cameraQuat); + strokeWidths.push(LINE_WIDTH); - var lineProps = Entities.getEntityProperties(this.lineID); - Entities.editEntity(this.lineID, { - linePoints: linePoints, - normals: normals, - strokeWidths: strokeWidths, - position: tipPosition, - }); - } - }, - - createOffsetAction: function() { - this.removeActions(); - - Entities.addAction("offset", this.ballID, { - pointToOffsetFrom: this.getTipPosition(), - linearDistance: ACTION_DISTANCE, - linearTimeScale: ACTION_TIMESCALE + var lineProps = Entities.getEntityProperties(this.lineID); + Entities.editEntity(this.lineID, { + linePoints: linePoints, + normals: normals, + strokeWidths: strokeWidths, + position: tipPosition, }); - }, - - createSpringAction: function() { - this.removeActions(); - - Entities.addAction("spring", this.ballID, { - targetPosition: this.getTipPosition(), - linearTimeScale: ACTION_TIMESCALE - }); - }, - - updateOffsetAction: function() { - var actionIDs = Entities.getActionIDs(this.ballID); - var actionID; - - // Sometimes two offset actions are applied to the ball simultaneously. - // Here we ensure that only the most recent action is updated - // and the rest are deleted. - while(actionIDs.length > 1) { - actionID = actionIDs.shift(); - Entities.deleteAction(this.ballID, actionID); - } - - actionID = actionIDs.shift(); - if(actionID) { - Entities.updateAction(this.ballID, actionID, { - pointToOffsetFrom: this.getTipPosition() - }); - } - }, - - removeActions: function() { - // Ball should only ever have one action, but sometimes sneaky little actions attach themselves - // So we remove all possible actions in this call. - var actionIDs = Entities.getActionIDs(this.ballID); - for(var i = 0; i < actionIDs.length; i++) { - Entities.deleteAction(this.ballID, actionIDs[i]); - } - }, - - hasAction: function() { - return Entities.getActionIDs(this.ballID).length > 0; } }; From a0c8c4df521da426f81d516cb4b2fee06ff5ac3b Mon Sep 17 00:00:00 2001 From: Rob Kayson Date: Tue, 18 Apr 2017 10:07:02 -0700 Subject: [PATCH 4/4] tetherball stick and ball remove themselves after 2 seconds if user who has them equipped leaves the domain --- .../entity_scripts/tetherballStick.js | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/scripts/tutorials/entity_scripts/tetherballStick.js b/scripts/tutorials/entity_scripts/tetherballStick.js index 9250675d50..867074abd4 100644 --- a/scripts/tutorials/entity_scripts/tetherballStick.js +++ b/scripts/tutorials/entity_scripts/tetherballStick.js @@ -17,6 +17,7 @@ (function() { var _this; + var LIFETIME_IF_LEAVE_DOMAIN = 2; var LINE_WIDTH = 0.02; var COLLISION_SOUND_URL = "http://public.highfidelity.io/sounds/Footsteps/FootstepW3Left-12db.wav"; var TIP_OFFSET = 0.26; @@ -46,12 +47,6 @@ this.lineID = userData.lineID; this.actionID = userData.actionID; this.maxDistanceBetweenBallAndStick = userData.maxDistanceBetweenBallAndStick; - - // Script.update.connect(this.update); - }, - - unload: function() { - // Script.update.disconnect(this.update); }, update: function(dt) { @@ -63,12 +58,24 @@ }, continueEquip: function(id, params) { + var stickProps = Entities.getEntityProperties(this.entityID); + [this.entityID, this.ballID, this.lineID].forEach(function(id) { + Entities.editEntity(id, { + lifetime: stickProps.age + LIFETIME_IF_LEAVE_DOMAIN + }); + }); this.updateOffsetAction(); this.capBallDistance(); this.drawLine(); }, releaseEquip: function() { + var userData = this.getUserData(); + [this.entityID, this.ballID, this.lineID].forEach(function(id) { + Entities.editEntity(id, { + lifetime: userData.lifetime + }); + }); Script.update.connect(this.update); },