2017-03-13 10:15:06 -07:00
"use strict" ;
2017-04-29 13:58:20 -07:00
/*jslint vars:true, plusplus:true, forin:true*/
/*global Window, Script, Controller, MyAvatar, AvatarList, Entities, Messages, Audio, SoundCache, Account, UserActivityLogger, Vec3, Quat, XMLHttpRequest, location, print*/
2017-03-13 10:15:06 -07:00
//
2017-04-26 18:51:55 +02:00
// makeUserConnection.js
2017-03-14 08:58:14 -07:00
// scripts/system
2017-03-13 10:15:06 -07:00
//
// Created by David Kelly on 3/7/2017.
// 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
//
2017-05-01 08:07:16 -07:00
2017-04-29 13:58:20 -07:00
( function ( ) { // BEGIN LOCAL_SCOPE
2017-03-17 08:45:24 -07:00
2017-05-03 07:51:49 -07:00
var request = Script . require ( 'request' ) . request ;
2017-03-17 08:45:24 -07:00
2023-11-30 00:26:01 +01:00
var controllerStandard = Controller . Standard ;
2017-09-20 14:59:15 -07:00
var WANT _DEBUG = Settings . getValue ( 'MAKE_USER_CONNECTION_DEBUG' , false ) ;
2017-04-26 18:51:55 +02:00
var LABEL = "makeUserConnection" ;
var MAX _AVATAR _DISTANCE = 0.2 ; // m
2017-04-28 16:39:58 -07:00
var GRIP _MIN = 0.75 ; // goes from 0-1, so 75% pressed is pressed
2017-04-26 18:51:55 +02:00
var MESSAGE _CHANNEL = "io.highfidelity.makeUserConnection" ;
var STATES = {
INACTIVE : 0 ,
WAITING : 1 ,
CONNECTING : 2 ,
MAKING _CONNECTION : 3
} ;
var STATE _STRINGS = [ "inactive" , "waiting" , "connecting" , "makingConnection" ] ;
2017-04-29 18:25:33 -07:00
var HAND _STRING _PROPERTY = 'hand' ; // Used in our message protocol. IWBNI we changed it to handString, but that would break compatability.
2017-04-26 18:51:55 +02:00
var WAITING _INTERVAL = 100 ; // ms
var CONNECTING _INTERVAL = 100 ; // ms
var MAKING _CONNECTION _TIMEOUT = 800 ; // ms
2018-09-27 17:05:18 -07:00
var CONNECTING _TIME = 100 ; // ms One interval.
2017-04-26 18:51:55 +02:00
var PARTICLE _RADIUS = 0.15 ; // m
2017-04-29 13:58:20 -07:00
var PARTICLE _ANGLE _INCREMENT = 360 / 45 ; // 1hz
2023-08-22 14:01:52 -04:00
var HANDSHAKE _SOUND _URL = Script . resolvePath ( "assets/sounds/4beat_sweep.wav" ) ;
var SUCCESSFUL _HANDSHAKE _SOUND _URL = Script . resolvePath ( "assets/sounds/3rdbeat_success_bell.wav" ) ;
var PARTICLE _TEXTURE = Script . resolvePath ( "assets/images/Bokeh-Particle.png" ) ;
2017-04-26 18:55:50 +02:00
var PREFERRER _HAND _JOINT _POSTFIX _ORDER = [ 'Middle1' , 'Index1' , '' ] ;
2017-04-26 18:51:55 +02:00
var HAPTIC _DATA = {
initial : { duration : 20 , strength : 0.6 } , // duration is in ms
background : { duration : 100 , strength : 0.3 } , // duration is in ms
success : { duration : 60 , strength : 1.0 } // duration is in ms
} ;
var PARTICLE _EFFECT _PROPS = {
"alpha" : 0.8 ,
"azimuthFinish" : Math . PI ,
2017-12-12 17:35:37 +13:00
"azimuthStart" : - Math . PI ,
2017-04-26 18:51:55 +02:00
"emitRate" : 500 ,
"emitterShouldTrail" : 1 ,
"isEmitting" : 1 ,
"lifespan" : 3 ,
2018-12-10 14:08:38 -08:00
"lifetime" : 5 ,
2017-04-26 18:51:55 +02:00
"maxParticles" : 1000 ,
"particleRadius" : 0.003 ,
2017-12-12 17:35:37 +13:00
"polarStart" : Math . PI / 2 ,
"polarFinish" : Math . PI / 2 ,
2017-04-26 18:51:55 +02:00
"radiusFinish" : 0.008 ,
"radiusStart" : 0.0025 ,
2017-12-12 17:35:37 +13:00
"emitSpeed" : 0.02 ,
"speedSpread" : 0.015 ,
2023-08-22 14:01:52 -04:00
"textures" : PARTICLE _TEXTURE ,
2017-04-26 18:51:55 +02:00
"color" : { "red" : 255 , "green" : 255 , "blue" : 255 } ,
"colorFinish" : { "red" : 0 , "green" : 164 , "blue" : 255 } ,
"colorStart" : { "red" : 255 , "green" : 255 , "blue" : 255 } ,
2017-04-29 13:58:20 -07:00
"emitOrientation" : { "w" : - 0.71 , "x" : 0.0 , "y" : 0.0 , "z" : 0.71 } ,
2017-04-26 18:51:55 +02:00
"emitAcceleration" : { "x" : 0.0 , "y" : 0.0 , "z" : 0.0 } ,
2017-12-12 17:35:37 +13:00
"emitDimensions" : { "x" : 0.15 , "y" : 0.15 , "z" : 0.01 } ,
2017-04-26 18:51:55 +02:00
"accelerationSpread" : { "x" : 0.0 , "y" : 0.0 , "z" : 0.0 } ,
2017-04-29 13:58:20 -07:00
"dimensions" : { "x" : 0.05 , "y" : 0.05 , "z" : 0.05 } ,
2017-04-26 18:51:55 +02:00
"type" : "ParticleEffect"
} ;
var MAKING _CONNECTION _PARTICLE _PROPS = {
"alpha" : 0.07 ,
"alphaStart" : 0.011 ,
"alphaSpread" : 0 ,
"alphaFinish" : 0 ,
"azimuthFinish" : Math . PI ,
2017-04-29 13:58:20 -07:00
"azimuthStart" : - 1 * Math . PI ,
2017-04-26 18:51:55 +02:00
"emitRate" : 2000 ,
"emitSpeed" : 0.0 ,
"emitterShouldTrail" : 1 ,
"isEmitting" : 1 ,
"lifespan" : 3.6 ,
2018-12-10 14:08:38 -08:00
"lifetime" : 5 ,
2017-04-26 18:51:55 +02:00
"maxParticles" : 4000 ,
"particleRadius" : 0.048 ,
"polarStart" : 0 ,
"polarFinish" : 1 ,
"radiusFinish" : 0.3 ,
"radiusStart" : 0.04 ,
2018-06-26 15:45:18 -07:00
"speedSpread" : 0.00 ,
"radiusSpread" : 0.0 ,
2023-08-22 14:01:52 -04:00
"textures" : PARTICLE _TEXTURE ,
2017-04-26 18:51:55 +02:00
"color" : { "red" : 200 , "green" : 170 , "blue" : 255 } ,
"colorFinish" : { "red" : 0 , "green" : 134 , "blue" : 255 } ,
"colorStart" : { "red" : 185 , "green" : 222 , "blue" : 255 } ,
2017-04-29 13:58:20 -07:00
"emitOrientation" : { "w" : - 0.71 , "x" : 0.0 , "y" : 0.0 , "z" : 0.71 } ,
2017-04-26 18:51:55 +02:00
"emitAcceleration" : { "x" : 0.0 , "y" : 0.0 , "z" : 0.0 } ,
"accelerationSpread" : { "x" : 0.0 , "y" : 0.0 , "z" : 0.0 } ,
2017-04-29 13:58:20 -07:00
"dimensions" : { "x" : 0.05 , "y" : 0.05 , "z" : 0.05 } ,
2017-04-26 18:51:55 +02:00
"type" : "ParticleEffect"
} ;
2017-04-29 13:58:20 -07:00
var currentHand ;
2017-04-26 18:55:50 +02:00
var currentHandJointIndex = - 1 ;
2017-04-26 18:51:55 +02:00
var state = STATES . INACTIVE ;
var connectingInterval ;
var waitingInterval ;
var makingConnectionTimeout ;
var animHandlerId ;
var connectingId ;
2017-04-26 18:55:50 +02:00
var connectingHandJointIndex = - 1 ;
2017-04-26 18:51:55 +02:00
var waitingList = { } ;
var particleEffect ;
2017-12-12 16:39:49 +13:00
var particleEmitRate ;
2017-12-13 08:56:36 +13:00
var PARTICLE _INITIAL _EMIT _RATE = 250 ;
var PARTICLE _MINIMUM _EMIT _RATE = 50 ;
2017-12-12 16:39:49 +13:00
var PARTICLE _DECAY _RATE = 0.5 ;
var particleEffectUpdateTimer = null ;
2017-12-12 17:35:37 +13:00
var PARTICLE _EFFECT _UPDATE _INTERVAL = 200 ;
2017-04-26 18:51:55 +02:00
var makingConnectionParticleEffect ;
2017-12-12 16:39:49 +13:00
var makingConnectionEmitRate ;
var isMakingConnectionEmitting ;
2017-12-13 08:56:36 +13:00
var MAKING _CONNECTION _INITIAL _EMIT _RATE = 500 ;
var MAKING _CONNECTION _MINIMUM _EMIT _RATE = 20 ;
2017-12-12 16:39:49 +13:00
var MAKING _CONNECTION _DECAY _RATE = 0.5 ;
var makingConnectionUpdateTimer = null ;
2017-12-12 17:35:37 +13:00
var MAKING _CONNECTION _UPDATE _INTERVAL = 200 ;
2017-04-26 18:51:55 +02:00
var handshakeInjector ;
var successfulHandshakeInjector ;
var handshakeSound ;
var successfulHandshakeSound ;
function debug ( ) {
2017-09-20 14:59:15 -07:00
if ( ! WANT _DEBUG ) {
return ;
}
2017-04-26 18:51:55 +02:00
var stateString = "<" + STATE _STRINGS [ state ] + ">" ;
2017-04-30 15:50:30 -07:00
var connecting = "[" + connectingId + "/" + connectingHandJointIndex + "]" ;
2017-05-04 12:38:17 -07:00
var current = "[" + currentHand + "/" + currentHandJointIndex + "]"
print . apply ( null , [ ] . concat . apply ( [ LABEL , stateString , current , JSON . stringify ( waitingList ) , connecting ] ,
2017-04-26 18:51:55 +02:00
[ ] . map . call ( arguments , JSON . stringify ) ) ) ;
}
function cleanId ( guidWithCurlyBraces ) {
return guidWithCurlyBraces . slice ( 1 , - 1 ) ;
}
function handToString ( hand ) {
2023-11-30 00:26:01 +01:00
if ( hand === controllerStandard . RightHand ) {
2017-04-26 18:51:55 +02:00
return "RightHand" ;
2017-04-29 13:58:20 -07:00
}
2023-11-30 00:26:01 +01:00
if ( hand === controllerStandard . LeftHand ) {
2017-04-26 18:51:55 +02:00
return "LeftHand" ;
}
2017-04-26 22:11:56 +02:00
debug ( "handToString called without valid hand! value: " , hand ) ;
2017-04-26 18:51:55 +02:00
return "" ;
}
function handToHaptic ( hand ) {
2023-11-30 00:26:01 +01:00
if ( hand === controllerStandard . RightHand ) {
2017-04-26 18:51:55 +02:00
return 1 ;
2017-04-29 13:58:20 -07:00
}
2023-11-30 00:26:01 +01:00
if ( hand === controllerStandard . LeftHand ) {
2017-04-26 18:51:55 +02:00
return 0 ;
}
debug ( "handToHaptic called without a valid hand!" ) ;
return - 1 ;
}
function stopWaiting ( ) {
if ( waitingInterval ) {
waitingInterval = Script . clearInterval ( waitingInterval ) ;
}
2017-03-16 13:19:17 -07:00
}
2017-04-26 18:51:55 +02:00
function stopConnecting ( ) {
if ( connectingInterval ) {
connectingInterval = Script . clearInterval ( connectingInterval ) ;
}
2017-03-16 13:19:17 -07:00
}
2017-04-26 18:51:55 +02:00
function stopMakingConnection ( ) {
if ( makingConnectionTimeout ) {
makingConnectionTimeout = Script . clearTimeout ( makingConnectionTimeout ) ;
}
2017-03-16 13:19:17 -07:00
}
2017-04-26 18:55:50 +02:00
// This returns the ideal hand joint index for the avatar.
2017-04-29 18:25:33 -07:00
// [handString]middle1 -> [handString]index1 -> [handString]
2017-04-30 15:50:30 -07:00
function getIdealHandJointIndex ( avatar , handString ) {
debug ( "get hand " + handString + " for avatar " + ( avatar && avatar . sessionUUID ) ) ;
var suffixIndex , jointName , jointIndex ;
for ( suffixIndex = 0 ; suffixIndex < ( avatar ? PREFERRER _HAND _JOINT _POSTFIX _ORDER . length : 0 ) ; suffixIndex ++ ) {
2017-04-29 13:58:20 -07:00
jointName = handString + PREFERRER _HAND _JOINT _POSTFIX _ORDER [ suffixIndex ] ;
jointIndex = avatar . getJointIndex ( jointName ) ;
2017-04-26 18:55:50 +02:00
if ( jointIndex !== - 1 ) {
2017-04-26 21:42:36 +02:00
debug ( 'found joint ' + jointName + ' (' + jointIndex + ')' ) ;
2017-04-26 18:55:50 +02:00
return jointIndex ;
}
}
2017-04-26 21:42:36 +02:00
debug ( 'no hand joint found.' ) ;
2017-04-26 18:55:50 +02:00
return - 1 ;
}
// This returns the preferred hand position.
function getHandPosition ( avatar , handJointIndex ) {
if ( handJointIndex === - 1 ) {
debug ( "calling getHandPosition with no hand joint index! (returning avatar position but this is a BUG)" ) ;
2017-04-26 18:51:55 +02:00
return avatar . position ;
}
2017-04-26 18:55:50 +02:00
return avatar . getJointPosition ( handJointIndex ) ;
2017-04-26 18:51:55 +02:00
}
2017-04-29 14:39:35 -07:00
var animationData = { } ;
2017-05-11 14:20:42 -07:00
function updateAnimationData ( verticalOffset ) {
2017-04-29 15:50:04 -07:00
// all we are doing here is moving the right hand to a spot
// that is in front of and a bit above the hips. Basing how
// far in front as scaling with the avatar's height (say hips
// to head distance)
var headIndex = MyAvatar . getJointIndex ( "Head" ) ;
var offset = 0.5 ; // default distance of hand in front of you
if ( headIndex ) {
offset = 0.8 * MyAvatar . getAbsoluteJointTranslationInObjectFrame ( headIndex ) . y ;
}
animationData . rightHandPosition = Vec3 . multiply ( offset , { x : - 0.25 , y : 0.8 , z : 1.3 } ) ;
2017-05-11 14:20:42 -07:00
if ( verticalOffset ) {
animationData . rightHandPosition . y += verticalOffset ;
}
2017-04-29 15:50:04 -07:00
animationData . rightHandRotation = Quat . fromPitchYawRollDegrees ( 90 , 0 , 90 ) ;
2018-08-13 11:57:48 -07:00
animationData . rightHandType = 0 ; // RotationAndPosition, see IKTargets.h
2018-11-19 17:59:50 -08:00
// turn on the right hand grip overlay
animationData . rightHandOverlayAlpha = 1.0 ;
// make sure the right hand grip animation is the "grasp", not pointing or thumbs up.
animationData . isRightHandGrasp = true ;
animationData . isRightIndexPoint = false ;
animationData . isRightThumbRaise = false ;
animationData . isRightIndexPointAndThumbRaise = false ;
2017-04-29 15:50:04 -07:00
}
2017-04-29 13:58:20 -07:00
function shakeHandsAnimation ( ) {
2017-04-29 14:39:35 -07:00
return animationData ;
}
function endHandshakeAnimation ( ) {
if ( animHandlerId ) {
debug ( "removing animation" ) ;
animHandlerId = MyAvatar . removeAnimationStateHandler ( animHandlerId ) ;
}
}
function startHandshakeAnimation ( ) {
endHandshakeAnimation ( ) ; // just in case order of press/unpress is broken
debug ( "adding animation" ) ;
2017-04-29 15:50:04 -07:00
updateAnimationData ( ) ;
2017-04-29 14:39:35 -07:00
animHandlerId = MyAvatar . addAnimationStateHandler ( shakeHandsAnimation , [ ] ) ;
2017-04-26 18:51:55 +02:00
}
function positionFractionallyTowards ( posA , posB , frac ) {
return Vec3 . sum ( posA , Vec3 . multiply ( frac , Vec3 . subtract ( posB , posA ) ) ) ;
}
function deleteParticleEffect ( ) {
2017-12-12 16:39:49 +13:00
if ( particleEffectUpdateTimer ) {
Script . clearTimeout ( particleEffectUpdateTimer ) ;
particleEffectUpdateTimer = null ;
}
2017-04-26 18:51:55 +02:00
if ( particleEffect ) {
particleEffect = Entities . deleteEntity ( particleEffect ) ;
}
2017-03-13 10:15:06 -07:00
}
2017-04-26 18:51:55 +02:00
function deleteMakeConnectionParticleEffect ( ) {
2017-12-12 16:39:49 +13:00
if ( makingConnectionUpdateTimer ) {
Script . clearTimeout ( makingConnectionUpdateTimer ) ;
makingConnectionUpdateTimer = null ;
}
2017-04-26 18:51:55 +02:00
if ( makingConnectionParticleEffect ) {
makingConnectionParticleEffect = Entities . deleteEntity ( makingConnectionParticleEffect ) ;
2017-03-15 11:15:58 -07:00
}
}
2017-03-22 10:09:48 -07:00
2017-04-26 18:51:55 +02:00
function stopHandshakeSound ( ) {
if ( handshakeInjector ) {
handshakeInjector . stop ( ) ;
handshakeInjector = null ;
}
}
2017-12-12 16:39:49 +13:00
function updateMakingConnection ( ) {
2018-12-10 14:08:38 -08:00
if ( ! makingConnectionParticleEffect ) {
particleEffectUpdateTimer = null ;
return ;
}
2017-12-12 16:39:49 +13:00
makingConnectionEmitRate = Math . max ( makingConnectionEmitRate * MAKING _CONNECTION _DECAY _RATE ,
2017-12-13 08:56:36 +13:00
MAKING _CONNECTION _MINIMUM _EMIT _RATE ) ;
2017-12-12 16:39:49 +13:00
isMakingConnectionEmitting = true ;
Entities . editEntity ( makingConnectionParticleEffect , {
emitRate : makingConnectionEmitRate ,
isEmitting : true
} ) ;
2017-12-13 08:56:36 +13:00
if ( makingConnectionEmitRate > MAKING _CONNECTION _MINIMUM _EMIT _RATE ) {
2017-12-12 16:39:49 +13:00
makingConnectionUpdateTimer = Script . setTimeout ( makingConnectionUpdateTimer , MAKING _CONNECTION _UPDATE _INTERVAL ) ;
} else {
makingConnectionUpdateTimer = null ;
}
}
function updateParticleEffect ( ) {
2018-12-10 14:08:38 -08:00
if ( ! particleEffect ) {
particleEffectUpdateTimer = null ;
return ;
}
2017-12-13 08:56:36 +13:00
particleEmitRate = Math . max ( PARTICLE _MINIMUM _EMIT _RATE , particleEmitRate * PARTICLE _DECAY _RATE ) ;
2017-12-12 16:39:49 +13:00
Entities . editEntity ( particleEffect , {
emitRate : particleEmitRate
} ) ;
2017-12-13 08:56:36 +13:00
if ( particleEmitRate > PARTICLE _MINIMUM _EMIT _RATE ) {
2017-12-12 16:39:49 +13:00
particleEffectUpdateTimer = Script . setTimeout ( updateParticleEffect , PARTICLE _EFFECT _UPDATE _INTERVAL ) ;
} else {
particleEffectUpdateTimer = null ;
2017-04-26 18:51:55 +02:00
}
}
// this is called frequently, but usually does nothing
function updateVisualization ( ) {
if ( state === STATES . INACTIVE ) {
2017-03-21 13:27:00 -07:00
deleteParticleEffect ( ) ;
2017-03-22 10:09:48 -07:00
deleteMakeConnectionParticleEffect ( ) ;
2017-04-26 18:51:55 +02:00
return ;
}
2017-04-26 18:55:50 +02:00
var myHandPosition = getHandPosition ( MyAvatar , currentHandJointIndex ) ;
2017-04-29 18:25:33 -07:00
var otherHandPosition ;
2017-04-26 18:51:55 +02:00
var otherOrientation ;
if ( connectingId ) {
var other = AvatarList . getAvatar ( connectingId ) ;
if ( other ) {
otherOrientation = other . orientation ;
2017-04-29 18:25:33 -07:00
otherHandPosition = getHandPosition ( other , connectingHandJointIndex ) ;
2017-03-21 13:27:00 -07:00
}
2017-04-26 18:51:55 +02:00
}
switch ( state ) {
2017-04-29 13:58:20 -07:00
case STATES . WAITING :
// no visualization while waiting
deleteParticleEffect ( ) ;
deleteMakeConnectionParticleEffect ( ) ;
stopHandshakeSound ( ) ;
break ;
case STATES . CONNECTING :
var particleProps = { } ;
// put the position between the 2 hands, if we have a connectingId. This
// helps define the plane in which the particles move.
2017-04-29 18:25:33 -07:00
positionFractionallyTowards ( myHandPosition , otherHandPosition , 0.5 ) ;
2017-04-29 13:58:20 -07:00
// now manage the rest of the entity
if ( ! particleEffect ) {
2017-12-13 08:56:36 +13:00
particleEmitRate = PARTICLE _INITIAL _EMIT _RATE ;
2017-04-29 13:58:20 -07:00
particleProps = PARTICLE _EFFECT _PROPS ;
2017-12-12 16:39:49 +13:00
particleProps . position = positionFractionallyTowards ( myHandPosition , otherHandPosition , 0.5 ) ;
2017-12-12 17:35:37 +13:00
particleProps . rotation = Vec3 . mix ( Quat . getFront ( MyAvatar . orientation ) ,
Quat . inverse ( Quat . getFront ( otherOrientation ) ) , 0.5 ) ;
2017-04-29 13:58:20 -07:00
particleProps . parentID = MyAvatar . sessionUUID ;
particleEffect = Entities . addEntity ( particleProps , true ) ;
}
if ( ! makingConnectionParticleEffect ) {
var props = MAKING _CONNECTION _PARTICLE _PROPS ;
props . parentID = MyAvatar . sessionUUID ;
2017-12-13 08:56:36 +13:00
makingConnectionEmitRate = MAKING _CONNECTION _INITIAL _EMIT _RATE ;
2017-04-29 13:58:20 -07:00
props . emitRate = makingConnectionEmitRate ;
2017-12-12 16:39:49 +13:00
props . isEmitting = false ;
2017-04-29 13:58:20 -07:00
props . position = myHandPosition ;
makingConnectionParticleEffect = Entities . addEntity ( props , true ) ;
2017-12-12 16:39:49 +13:00
makingConnectionUpdateTimer = Script . setTimeout ( updateMakingConnection , MAKING _CONNECTION _UPDATE _INTERVAL ) ;
2017-04-29 13:58:20 -07:00
}
break ;
case STATES . MAKING _CONNECTION :
2017-12-12 16:39:49 +13:00
if ( makingConnectionUpdateTimer ) {
Script . clearTimeout ( makingConnectionUpdateTimer ) ;
makingConnectionUpdateTimer = null ;
}
if ( isMakingConnectionEmitting ) {
Entities . editEntity ( makingConnectionParticleEffect , { isEmitting : false } ) ;
isMakingConnectionEmitting = false ;
}
2017-12-13 08:56:36 +13:00
if ( ! particleEffectUpdateTimer && particleEmitRate > PARTICLE _MINIMUM _EMIT _RATE ) {
2017-12-12 16:39:49 +13:00
particleEffectUpdateTimer = Script . setTimeout ( updateParticleEffect , PARTICLE _EFFECT _UPDATE _INTERVAL ) ;
}
2017-04-29 13:58:20 -07:00
break ;
default :
debug ( "unexpected state" , state ) ;
break ;
2017-04-26 18:51:55 +02:00
}
}
2017-04-30 14:19:33 -07:00
function isNearby ( ) {
2017-04-26 18:51:55 +02:00
if ( currentHand ) {
2017-04-29 18:25:33 -07:00
var handPosition = getHandPosition ( MyAvatar , currentHandJointIndex ) ;
2017-04-30 14:19:33 -07:00
var avatar = AvatarList . getAvatar ( connectingId ) ;
2017-04-26 18:51:55 +02:00
if ( avatar ) {
2017-04-30 14:19:33 -07:00
var distance = Vec3 . distance ( getHandPosition ( avatar , connectingHandJointIndex ) , handPosition ) ;
2017-04-26 18:51:55 +02:00
return ( distance < MAX _AVATAR _DISTANCE ) ;
2017-03-21 13:27:00 -07:00
}
2017-04-26 18:51:55 +02:00
}
return false ;
}
2017-05-11 14:20:42 -07:00
function findNearestAvatar ( ) {
// We only look some max distance away (much larger than the handshake distance, but still...)
var minDistance = MAX _AVATAR _DISTANCE * 20 ;
var closestAvatar ;
AvatarList . getAvatarIdentifiers ( ) . forEach ( function ( id ) {
var avatar = AvatarList . getAvatar ( id ) ;
if ( avatar && avatar . sessionUUID != MyAvatar . sessionUUID ) {
var currentDistance = Vec3 . distance ( avatar . position , MyAvatar . position ) ;
if ( minDistance > currentDistance ) {
minDistance = currentDistance ;
closestAvatar = avatar ;
}
}
} ) ;
return closestAvatar ;
}
function adjustAnimationHeight ( ) {
var avatar = findNearestAvatar ( ) ;
if ( avatar ) {
var myHeadIndex = MyAvatar . getJointIndex ( "Head" ) ;
var otherHeadIndex = avatar . getJointIndex ( "Head" ) ;
var diff = ( avatar . getJointPosition ( otherHeadIndex ) . y - MyAvatar . getJointPosition ( myHeadIndex ) . y ) / 2 ;
2017-09-20 14:39:13 -07:00
debug ( "head height difference: " + diff ) ;
2017-05-11 14:20:42 -07:00
updateAnimationData ( diff ) ;
}
}
2017-04-26 18:51:55 +02:00
function findNearestWaitingAvatar ( ) {
2017-04-29 18:25:33 -07:00
var handPosition = getHandPosition ( MyAvatar , currentHandJointIndex ) ;
2017-04-26 18:51:55 +02:00
var minDistance = MAX _AVATAR _DISTANCE ;
var nearestAvatar = { } ;
Object . keys ( waitingList ) . forEach ( function ( identifier ) {
var avatar = AvatarList . getAvatar ( identifier ) ;
if ( avatar ) {
2017-04-30 15:50:30 -07:00
var handJointIndex = waitingList [ identifier ] ;
2017-04-29 18:25:33 -07:00
var distance = Vec3 . distance ( getHandPosition ( avatar , handJointIndex ) , handPosition ) ;
2017-04-26 18:51:55 +02:00
if ( distance < minDistance ) {
minDistance = distance ;
2017-04-30 15:50:30 -07:00
nearestAvatar = { avatarId : identifier , jointIndex : handJointIndex } ;
2017-04-26 18:51:55 +02:00
}
2017-03-13 10:15:06 -07:00
}
2017-03-13 18:33:36 -07:00
} ) ;
2017-04-26 18:51:55 +02:00
return nearestAvatar ;
}
2017-04-29 13:58:20 -07:00
function messageSend ( message ) {
2017-05-09 17:33:33 -07:00
// we always append whether or not we are logged in...
message . isLoggedIn = Account . isLoggedIn ( ) ;
2017-04-29 13:58:20 -07:00
Messages . sendMessage ( MESSAGE _CHANNEL , JSON . stringify ( message ) ) ;
}
2017-04-30 15:50:30 -07:00
function handStringMessageSend ( message ) {
message [ HAND _STRING _PROPERTY ] = handToString ( currentHand ) ;
2017-04-29 18:25:33 -07:00
messageSend ( message ) ;
}
2017-04-30 13:32:21 -07:00
function setupCandidate ( ) { // find the closest in-range avatar, send connection request, and return true. (Otherwise falsey)
2017-04-29 19:39:50 -07:00
var nearestAvatar = findNearestWaitingAvatar ( ) ;
2017-04-30 15:50:30 -07:00
if ( nearestAvatar . avatarId ) {
connectingId = nearestAvatar . avatarId ;
connectingHandJointIndex = nearestAvatar . jointIndex ;
2017-04-29 19:39:50 -07:00
debug ( "sending connectionRequest to" , connectingId ) ;
handStringMessageSend ( {
key : "connectionRequest" ,
id : connectingId
2017-04-30 15:50:30 -07:00
} ) ;
2017-04-29 19:39:50 -07:00
return true ;
}
}
2017-04-30 08:04:46 -07:00
function clearConnecting ( ) {
connectingId = undefined ;
connectingHandJointIndex = - 1 ;
}
2017-04-29 13:58:20 -07:00
function lookForWaitingAvatar ( ) {
// we started with nobody close enough, but maybe I've moved
// or they did. Note that 2 people doing this race, so stop
// as soon as you have a connectingId (which means you got their
// message before noticing they were in range in this loop)
2017-04-26 18:51:55 +02:00
2017-04-29 13:58:20 -07:00
// just in case we re-enter before stopping
stopWaiting ( ) ;
debug ( "started looking for waiting avatars" ) ;
waitingInterval = Script . setInterval ( function ( ) {
if ( state === STATES . WAITING && ! connectingId ) {
2017-04-29 19:39:50 -07:00
setupCandidate ( ) ;
2017-04-29 13:58:20 -07:00
} else {
// something happened, stop looking for avatars to connect
stopWaiting ( ) ;
debug ( "stopped looking for waiting avatars" ) ;
}
} , WAITING _INTERVAL ) ;
}
2017-04-26 18:51:55 +02:00
2017-10-24 10:52:27 -07:00
var pollCount = 0 , requestUrl = Account . metaverseServerURL + '/api/v1/user/connection_request' ;
2017-04-26 18:51:55 +02:00
// As currently implemented, we select the closest waiting avatar (if close enough) and send
// them a connectionRequest. If nobody is close enough we send a waiting message, and wait for a
// connectionRequest. If the 2 people who want to connect are both somewhat out of range when they
// initiate the shake, they will race to see who sends the connectionRequest after noticing the
2017-04-30 13:32:21 -07:00
// waiting message. Either way, they will start connecting each other at that point.
2017-04-26 18:51:55 +02:00
function startHandshake ( fromKeyboard ) {
if ( fromKeyboard ) {
2017-04-29 14:39:35 -07:00
startHandshakeAnimation ( ) ;
2017-04-26 18:51:55 +02:00
}
debug ( "starting handshake for" , currentHand ) ;
pollCount = 0 ;
state = STATES . WAITING ;
2017-04-30 08:04:46 -07:00
clearConnecting ( ) ;
2017-04-26 18:51:55 +02:00
// just in case
stopWaiting ( ) ;
stopConnecting ( ) ;
stopMakingConnection ( ) ;
2017-04-30 13:32:21 -07:00
if ( ! setupCandidate ( ) ) {
2017-04-26 18:51:55 +02:00
// send waiting message
debug ( "sending waiting message" ) ;
2017-04-29 18:25:33 -07:00
handStringMessageSend ( {
2017-04-26 18:51:55 +02:00
key : "waiting" ,
2017-04-30 15:50:30 -07:00
} ) ;
2017-05-11 14:20:42 -07:00
// potentially adjust height of handshake
if ( fromKeyboard ) {
adjustAnimationHeight ( ) ;
}
2017-04-26 18:51:55 +02:00
lookForWaitingAvatar ( ) ;
}
}
function endHandshake ( ) {
debug ( "ending handshake for" , currentHand ) ;
deleteParticleEffect ( ) ;
deleteMakeConnectionParticleEffect ( ) ;
currentHand = undefined ;
2017-04-26 18:55:50 +02:00
currentHandJointIndex = - 1 ;
2017-04-26 18:51:55 +02:00
// note that setting the state to inactive should really
// only be done here, unless we change how the triggering works,
// as we ignore the key release event when inactive. See updateTriggers
// below.
state = STATES . INACTIVE ;
2017-04-30 08:04:46 -07:00
clearConnecting ( ) ;
2017-04-26 18:51:55 +02:00
stopWaiting ( ) ;
stopConnecting ( ) ;
stopMakingConnection ( ) ;
stopHandshakeSound ( ) ;
// send done to let connection know you are not making connections now
2017-03-15 16:41:07 -07:00
messageSend ( {
2017-04-26 18:51:55 +02:00
key : "done"
2017-03-15 16:41:07 -07:00
} ) ;
2017-03-16 13:19:17 -07:00
2017-04-29 14:39:35 -07:00
endHandshakeAnimation ( ) ;
2017-04-26 18:51:55 +02:00
// No-op if we were successful, but this way we ensure that failures and abandoned handshakes don't leave us
// in a weird state.
2017-05-09 17:33:33 -07:00
if ( Account . isLoggedIn ( ) ) {
request ( { uri : requestUrl , method : 'DELETE' } , debug ) ;
}
2017-03-13 10:15:06 -07:00
}
2017-04-26 18:51:55 +02:00
function updateTriggers ( value , fromKeyboard , hand ) {
if ( currentHand && hand !== currentHand ) {
2017-05-02 11:29:51 -07:00
debug ( "currentHand" , currentHand , "ignoring messages from" , hand ) ; // this can be a lot of spam on Touch. Should guard that someday.
2017-03-13 10:15:06 -07:00
return ;
}
2017-04-26 18:51:55 +02:00
// ok now, we are either initiating or quitting...
var isGripping = value > GRIP _MIN ;
if ( isGripping ) {
debug ( "updateTriggers called - gripping" , handToString ( hand ) ) ;
if ( state !== STATES . INACTIVE ) {
return ;
}
2017-05-02 11:29:51 -07:00
currentHand = hand ;
currentHandJointIndex = getIdealHandJointIndex ( MyAvatar , handToString ( currentHand ) ) ; // Always, in case of changed skeleton.
2017-04-29 13:58:20 -07:00
startHandshake ( fromKeyboard ) ;
2017-03-13 10:15:06 -07:00
} else {
2017-04-26 18:51:55 +02:00
// TODO: should we end handshake even when inactive? Ponder
debug ( "updateTriggers called -- no longer gripping" , handToString ( hand ) ) ;
if ( state !== STATES . INACTIVE ) {
endHandshake ( ) ;
} else {
return ;
}
2017-03-13 10:15:06 -07:00
}
}
2017-04-26 18:51:55 +02:00
/ * T h e r e i s a m i n i - s t a t e m a c h i n e a f t e r e n t e r i n g S T A T E S . m a k i n g C o n n e c t i o n .
We make a request ( which might immediately succeed , fail , or neither .
If we immediately fail , we tell the user .
Otherwise , we wait MAKING _CONNECTION _TIMEOUT . At that time , we poll until success or fail .
* /
2017-04-29 13:58:20 -07:00
var result , requestBody ;
2017-04-26 18:51:55 +02:00
function connectionRequestCompleted ( ) { // Final result is in. Do effects.
if ( result . status === 'success' ) { // set earlier
if ( ! successfulHandshakeInjector ) {
successfulHandshakeInjector = Audio . playSound ( successfulHandshakeSound , {
2017-04-26 21:42:36 +02:00
position : getHandPosition ( MyAvatar , currentHandJointIndex ) ,
2017-04-26 18:51:55 +02:00
volume : 0.5 ,
localOnly : true
2017-03-15 16:41:07 -07:00
} ) ;
2017-04-26 18:51:55 +02:00
} else {
successfulHandshakeInjector . restart ( ) ;
2017-03-15 16:41:07 -07:00
}
2017-04-26 18:51:55 +02:00
Controller . triggerHapticPulse ( HAPTIC _DATA . success . strength , HAPTIC _DATA . success . duration ,
handToHaptic ( currentHand ) ) ;
// don't change state (so animation continues while gripped)
// but do send a notification, by calling the slot that emits the signal for it
2017-04-29 13:58:20 -07:00
Window . makeConnection ( true ,
result . connection . new _connection ?
"You and " + result . connection . username + " are now connected!" :
result . connection . username ) ;
UserActivityLogger . makeUserConnection ( connectingId ,
true ,
result . connection . new _connection ?
"new connection" :
"already connected" ) ;
2017-04-26 18:51:55 +02:00
return ;
} // failed
endHandshake ( ) ;
debug ( "failing with result data" , result ) ;
// IWBNI we also did some fail sound/visual effect.
Window . makeConnection ( false , result . connection ) ;
2017-04-30 09:44:34 -07:00
if ( Account . isLoggedIn ( ) ) { // Give extra failure info
2017-10-24 10:52:27 -07:00
request ( Account . metaverseServerURL + '/api/v1/users/' + Account . username + '/location' , function ( error , response ) {
2017-04-30 09:44:34 -07:00
var message = '' ;
if ( error || response . status !== 'success' ) {
message = 'Unable to get location.' ;
} else if ( ! response . data || ! response . data . location ) {
message = "Unexpected location value: " + JSON . stringify ( response ) ;
} else if ( response . data . location . node _id !== cleanId ( MyAvatar . sessionUUID ) ) {
message = 'Session identification does not match database. Maybe you are logged in on another machine? That would prevent handshakes.' + JSON . stringify ( response ) + MyAvatar . sessionUUID ;
}
if ( message ) {
Window . makeConnection ( false , message ) ;
}
debug ( "account location:" , message || 'ok' ) ;
} ) ;
}
2017-04-26 18:51:55 +02:00
UserActivityLogger . makeUserConnection ( connectingId , false , result . connection ) ;
}
2017-04-28 16:39:58 -07:00
// This is a bit fragile - but to account for skew in when people actually create the
// connection request, I've upped this to 2 seconds (plus the round-trip times)
// TODO: keep track of when the person we are connecting with is done, and don't stop
// until say 1 second after that.
var POLL _INTERVAL _MS = 200 , POLL _LIMIT = 10 ;
2017-04-26 18:51:55 +02:00
function handleConnectionResponseAndMaybeRepeat ( error , response ) {
// If response is 'pending', set a short timeout to try again.
// If we fail other than pending, set result and immediately call connectionRequestCompleted.
// If we succeed, set result and call connectionRequestCompleted immediately (if we've been polling),
// and otherwise on a timeout.
if ( response && ( response . connection === 'pending' ) ) {
debug ( response , 'pollCount' , pollCount ) ;
if ( pollCount ++ >= POLL _LIMIT ) { // server will expire, but let's not wait that long.
debug ( 'POLL LIMIT REACHED; TIMEOUT: expired message generated by CLIENT' ) ;
2017-04-30 09:44:34 -07:00
result = { status : 'error' , connection : 'No logged-in partner found.' } ;
2017-04-26 18:51:55 +02:00
connectionRequestCompleted ( ) ;
} else { // poll
Script . setTimeout ( function ( ) {
request ( {
uri : requestUrl ,
// N.B.: server gives bad request if we specify json content type, so don't do that.
body : requestBody
} , handleConnectionResponseAndMaybeRepeat ) ;
} , POLL _INTERVAL _MS ) ;
}
} else if ( error || ( response . status !== 'success' ) ) {
debug ( 'server fail' , error , response . status ) ;
if ( response && ( response . statusCode === 401 ) ) {
error = "All participants must be logged in to connect." ;
}
result = error ? { status : 'error' , connection : error } : response ;
2017-03-29 12:22:26 -07:00
connectionRequestCompleted ( ) ;
2017-04-26 18:51:55 +02:00
} else {
result = response ;
2017-04-26 22:52:57 +02:00
debug ( 'server success' , result ) ;
2017-04-26 18:51:55 +02:00
if ( pollCount ++ ) {
connectionRequestCompleted ( ) ;
} else { // Wait for other guy, so that final success is at roughly the same time.
Script . setTimeout ( connectionRequestCompleted , MAKING _CONNECTION _TIMEOUT ) ;
}
2017-03-29 12:22:26 -07:00
}
}
2017-05-09 17:33:33 -07:00
function makeConnection ( id , isLoggedIn ) {
2017-04-26 18:51:55 +02:00
// send done to let the connection know you have made connection.
messageSend ( {
key : "done" ,
connectionId : id
} ) ;
2017-03-22 13:26:51 -07:00
2017-04-26 18:51:55 +02:00
state = STATES . MAKING _CONNECTION ;
2017-04-29 14:29:43 -07:00
// continue the haptic background until the timeout fires.
2017-04-26 18:51:55 +02:00
Controller . triggerHapticPulse ( HAPTIC _DATA . background . strength , MAKING _CONNECTION _TIMEOUT , handToHaptic ( currentHand ) ) ;
requestBody = { 'node_id' : cleanId ( MyAvatar . sessionUUID ) , 'proposed_node_id' : cleanId ( id ) } ; // for use when repeating
// It would be "simpler" to skip this and just look at the response, but:
2022-09-04 21:33:44 +02:00
// 1. We don't want to bother the directory server with request that we know will fail.
// 2. We don't want our code here to be dependent on precisely how the directory server responds (400, 401, etc.)
2017-05-09 17:33:33 -07:00
// 3. We also don't want to connect to someone who is anonymous _now_, but was not earlier and still has
// the same node id. Since the messaging doesn't say _who_ isn't logged in, fail the same as if we are
// not logged in.
2017-05-10 10:09:45 -07:00
if ( ! Account . isLoggedIn ( ) || isLoggedIn === false ) {
2017-04-26 18:51:55 +02:00
handleConnectionResponseAndMaybeRepeat ( "401:Unauthorized" , { statusCode : 401 } ) ;
return ;
}
// This will immediately set response if successful (e.g., the other guy got his request in first),
// or immediate failure, and will otherwise poll (using the requestBody we just set).
2017-04-29 13:58:20 -07:00
request ( {
2017-04-26 18:51:55 +02:00
uri : requestUrl ,
method : 'POST' ,
json : true ,
body : { 'user_connection_request' : requestBody }
} , handleConnectionResponseAndMaybeRepeat ) ;
}
2017-04-30 15:50:30 -07:00
function setupConnecting ( id , jointIndex ) {
2017-04-30 08:04:46 -07:00
connectingId = id ;
2017-04-30 15:50:30 -07:00
connectingHandJointIndex = jointIndex ;
2017-04-30 08:04:46 -07:00
}
2017-04-26 18:51:55 +02:00
// we change states, start the connectionInterval where we check
// to be sure the hand is still close enough. If not, we terminate
// the interval, go back to the waiting state. If we make it
2017-05-09 17:33:33 -07:00
// the entire CONNECTING_TIME, we make the connection. We pass in
2017-05-31 13:28:57 -07:00
// whether or not the connecting id is actually logged in, as now we
// will allow to start the connection process but have it stop with a
2017-05-09 17:33:33 -07:00
// fail message before trying to call the backend if the other guy isn't
// logged in.
function startConnecting ( id , jointIndex , isLoggedIn ) {
2017-04-26 18:51:55 +02:00
var count = 0 ;
2017-04-30 15:50:30 -07:00
debug ( "connecting" , id , "hand" , jointIndex ) ;
2017-04-26 18:51:55 +02:00
// do we need to do this?
2017-04-30 15:50:30 -07:00
setupConnecting ( id , jointIndex ) ;
2017-04-26 18:51:55 +02:00
state = STATES . CONNECTING ;
// play sound
if ( ! handshakeInjector ) {
handshakeInjector = Audio . playSound ( handshakeSound , {
2017-04-26 18:55:50 +02:00
position : getHandPosition ( MyAvatar , currentHandJointIndex ) ,
2017-04-26 18:51:55 +02:00
volume : 0.5 ,
localOnly : true
2017-03-13 18:33:36 -07:00
} ) ;
2017-03-16 13:19:17 -07:00
} else {
2017-04-26 18:51:55 +02:00
handshakeInjector . restart ( ) ;
}
// send message that we are connecting with them
2017-04-29 18:25:33 -07:00
handStringMessageSend ( {
2017-04-26 18:51:55 +02:00
key : "connecting" ,
2017-04-29 18:25:33 -07:00
id : id
2017-04-30 15:50:30 -07:00
} ) ;
2017-04-26 18:51:55 +02:00
Controller . triggerHapticPulse ( HAPTIC _DATA . initial . strength , HAPTIC _DATA . initial . duration , handToHaptic ( currentHand ) ) ;
connectingInterval = Script . setInterval ( function ( ) {
count += 1 ;
Controller . triggerHapticPulse ( HAPTIC _DATA . background . strength , HAPTIC _DATA . background . duration ,
handToHaptic ( currentHand ) ) ;
if ( state !== STATES . CONNECTING ) {
debug ( "stopping connecting interval, state changed" ) ;
stopConnecting ( ) ;
2017-04-30 14:19:33 -07:00
} else if ( ! isNearby ( ) ) {
2017-04-26 18:51:55 +02:00
// gotta go back to waiting
debug ( id , "moved, back to waiting" ) ;
stopConnecting ( ) ;
messageSend ( {
key : "done"
} ) ;
2017-03-16 13:19:17 -07:00
startHandshake ( ) ;
2017-04-29 13:58:20 -07:00
} else if ( count > CONNECTING _TIME / CONNECTING _INTERVAL ) {
2017-04-26 18:51:55 +02:00
debug ( "made connection with " + id ) ;
2017-05-09 17:33:33 -07:00
makeConnection ( id , isLoggedIn ) ;
2017-04-26 18:51:55 +02:00
stopConnecting ( ) ;
2017-03-16 13:19:17 -07:00
}
2017-04-26 18:51:55 +02:00
} , CONNECTING _INTERVAL ) ;
}
/ *
A simple sequence diagram : NOTE that the ConnectionAck is somewhat
vestigial , and probably should be removed shortly .
Avatar A Avatar B
| |
| < -- -- - ( waiting ) -- -- - startHandshake
startHandshake - ( connectionRequest ) - > |
| |
| < -- -- ( connectionAck ) -- -- -- -- |
| < -- -- - ( connecting ) -- startConnecting
startConnecting -- - ( connecting ) -- -- > |
| |
| connected
connected |
| < -- -- -- -- - ( done ) -- -- -- -- -- |
| -- -- -- -- -- ( done ) -- -- -- -- - > |
* /
function messageHandler ( channel , messageString , senderID ) {
2017-04-30 15:50:30 -07:00
var message = { } ;
function exisitingOrSearchedJointIndex ( ) { // If this is a new connectingId, we'll need to find the jointIndex
return connectingId ? connectingHandJointIndex : getIdealHandJointIndex ( AvatarList . getAvatar ( senderID ) , message [ HAND _STRING _PROPERTY ] ) ;
}
2017-04-26 18:51:55 +02:00
if ( channel !== MESSAGE _CHANNEL ) {
return ;
2017-03-13 18:33:36 -07:00
}
2017-04-26 18:51:55 +02:00
if ( MyAvatar . sessionUUID === senderID ) { // ignore my own
return ;
2017-03-13 18:33:36 -07:00
}
2017-04-26 18:51:55 +02:00
try {
message = JSON . parse ( messageString ) ;
} catch ( e ) {
debug ( e ) ;
}
switch ( message . key ) {
2017-04-29 13:58:20 -07:00
case "waiting" :
2017-04-30 15:50:30 -07:00
// add this guy to waiting object. Any other message from this person will remove it from the list
waitingList [ senderID ] = getIdealHandJointIndex ( AvatarList . getAvatar ( senderID ) , message [ HAND _STRING _PROPERTY ] ) ;
2017-04-29 13:58:20 -07:00
break ;
case "connectionRequest" :
delete waitingList [ senderID ] ;
if ( state === STATES . WAITING && message . id === MyAvatar . sessionUUID && ( ! connectingId || connectingId === senderID ) ) {
// you were waiting for a connection request, so send the ack. Or, you and the other
// guy raced and both send connectionRequests. Handle that too
2017-04-30 15:50:30 -07:00
setupConnecting ( senderID , exisitingOrSearchedJointIndex ( ) ) ;
2017-04-29 18:25:33 -07:00
handStringMessageSend ( {
2017-04-29 13:58:20 -07:00
key : "connectionAck" ,
id : senderID ,
2017-04-30 15:50:30 -07:00
} ) ;
2017-04-29 13:58:20 -07:00
} else if ( state === STATES . WAITING && connectingId === senderID ) {
// the person you are trying to connect sent a request to someone else. See the
// if statement above. So, don't cry, just start the handshake over again
startHandshake ( ) ;
}
break ;
case "connectionAck" :
delete waitingList [ senderID ] ;
if ( state === STATES . WAITING && ( ! connectingId || connectingId === senderID ) ) {
if ( message . id === MyAvatar . sessionUUID ) {
stopWaiting ( ) ;
2017-05-09 17:33:33 -07:00
startConnecting ( senderID , exisitingOrSearchedJointIndex ( ) , message . isLoggedIn ) ;
2017-04-29 13:58:20 -07:00
} else if ( connectingId ) {
// this is for someone else (we lost race in connectionRequest),
// so lets start over
2017-03-13 18:33:36 -07:00
startHandshake ( ) ;
}
2017-04-29 13:58:20 -07:00
}
// TODO: check to see if we are waiting for this but the person we are connecting sent it to
// someone else, and try again
break ;
case "connecting" :
delete waitingList [ senderID ] ;
if ( state === STATES . WAITING && senderID === connectingId ) {
if ( message . id !== MyAvatar . sessionUUID ) {
// the person we were trying to connect is connecting to someone else
// so try again
startHandshake ( ) ;
break ;
2017-04-26 18:51:55 +02:00
}
2017-05-09 17:33:33 -07:00
startConnecting ( senderID , connectingHandJointIndex , message . isLoggedIn ) ;
2017-04-29 13:58:20 -07:00
}
break ;
case "done" :
delete waitingList [ senderID ] ;
2017-05-09 13:27:42 -07:00
if ( connectingId !== senderID ) {
2017-05-04 16:33:50 -07:00
break ;
}
if ( state === STATES . CONNECTING ) {
2017-04-29 13:58:20 -07:00
// if they are done, and didn't connect us, terminate our
// connecting
if ( message . connectionId !== MyAvatar . sessionUUID ) {
stopConnecting ( ) ;
// now just call startHandshake. Should be ok to do so without a
// value for isKeyboard, as we should not change the animation
// state anyways (if any)
startHandshake ( ) ;
2017-05-04 12:38:17 -07:00
} else {
// they just created a connection request to us, and we are connecting to
// them, so lets just stop connecting and make connection..
2017-05-09 17:33:33 -07:00
makeConnection ( connectingId , message . isLoggedIn ) ;
2017-05-04 12:38:17 -07:00
stopConnecting ( ) ;
2017-04-29 13:58:20 -07:00
}
} else {
2017-05-04 16:33:50 -07:00
if ( state == STATES . MAKING _CONNECTION ) {
2017-05-04 12:38:17 -07:00
// we are making connection, they just started, so lets reset the
// poll count just in case
pollCount = 0 ;
} else {
// if waiting or inactive, lets clear the connecting id. If in makingConnection,
// do nothing
2017-04-30 08:04:46 -07:00
clearConnecting ( ) ;
2017-04-29 13:58:20 -07:00
if ( state !== STATES . INACTIVE ) {
2017-04-26 18:51:55 +02:00
startHandshake ( ) ;
}
}
2017-04-29 13:58:20 -07:00
}
break ;
default :
debug ( "unknown message" , message ) ;
break ;
2017-03-13 11:04:51 -07:00
}
2017-03-13 10:15:06 -07:00
}
2017-04-26 18:51:55 +02:00
Messages . subscribe ( MESSAGE _CHANNEL ) ;
Messages . messageReceived . connect ( messageHandler ) ;
2017-03-13 10:15:06 -07:00
2017-04-26 18:51:55 +02:00
function makeGripHandler ( hand , animate ) {
// determine if we are gripping or un-gripping
if ( animate ) {
return function ( value ) {
2017-04-29 13:58:20 -07:00
updateTriggers ( value , true , hand ) ;
2017-04-26 18:51:55 +02:00
} ;
}
2017-04-29 13:58:20 -07:00
return function ( value ) {
updateTriggers ( value , false , hand ) ;
} ;
2017-03-13 13:10:05 -07:00
}
2017-03-13 10:15:06 -07:00
2017-04-26 18:51:55 +02:00
function keyPressEvent ( event ) {
2017-12-14 08:52:21 +13:00
if ( ( event . text . toUpperCase ( ) === "X" ) && ! event . isAutoRepeat && ! event . isShifted && ! event . isMeta && ! event . isControl
&& ! event . isAlt ) {
2023-11-30 00:26:01 +01:00
updateTriggers ( 1.0 , true , controllerStandard . RightHand ) ;
2017-04-26 18:51:55 +02:00
}
2017-03-13 10:15:06 -07:00
}
2017-04-26 18:51:55 +02:00
function keyReleaseEvent ( event ) {
2017-12-17 15:23:40 +13:00
if ( event . text . toUpperCase ( ) === "X" && ! event . isAutoRepeat ) {
2023-11-30 00:26:01 +01:00
updateTriggers ( 0.0 , true , controllerStandard . RightHand ) ;
2017-04-26 18:51:55 +02:00
}
2017-03-13 10:15:06 -07:00
}
2017-04-26 18:51:55 +02:00
// map controller actions
var connectionMapping = Controller . newMapping ( Script . resolvePath ( '' ) + '-grip' ) ;
2023-11-30 00:26:01 +01:00
connectionMapping . from ( controllerStandard . LeftGrip ) . peek ( ) . to ( makeGripHandler ( controllerStandard . LeftHand ) ) ;
connectionMapping . from ( controllerStandard . RightGrip ) . peek ( ) . to ( makeGripHandler ( controllerStandard . RightHand ) ) ;
2017-03-13 10:15:06 -07:00
2017-04-26 18:51:55 +02:00
// setup keyboard initiation
Controller . keyPressEvent . connect ( keyPressEvent ) ;
Controller . keyReleaseEvent . connect ( keyReleaseEvent ) ;
2017-03-13 10:15:06 -07:00
2017-04-26 18:51:55 +02:00
// Xbox controller because that is important
2023-11-30 00:26:01 +01:00
connectionMapping . from ( controllerStandard . RB ) . peek ( ) . to ( makeGripHandler ( controllerStandard . RightHand , true ) ) ;
2017-03-13 13:10:05 -07:00
2017-04-26 18:51:55 +02:00
// it is easy to forget this and waste a lot of time for nothing
connectionMapping . enable ( ) ;
2017-03-13 10:15:06 -07:00
2017-04-26 18:51:55 +02:00
// connect updateVisualization to update frequently
Script . update . connect ( updateVisualization ) ;
2017-03-13 10:15:06 -07:00
2017-04-26 18:51:55 +02:00
// load the sounds when the script loads
handshakeSound = SoundCache . getSound ( HANDSHAKE _SOUND _URL ) ;
successfulHandshakeSound = SoundCache . getSound ( SUCCESSFUL _HANDSHAKE _SOUND _URL ) ;
2017-03-22 13:26:51 -07:00
2017-04-26 18:51:55 +02:00
Script . scriptEnding . connect ( function ( ) {
debug ( "removing controller mappings" ) ;
connectionMapping . disable ( ) ;
debug ( "removing key mappings" ) ;
Controller . keyPressEvent . disconnect ( keyPressEvent ) ;
Controller . keyReleaseEvent . disconnect ( keyReleaseEvent ) ;
debug ( "disconnecting updateVisualization" ) ;
Script . update . disconnect ( updateVisualization ) ;
deleteParticleEffect ( ) ;
deleteMakeConnectionParticleEffect ( ) ;
} ) ;
2017-03-13 10:15:06 -07:00
2017-03-14 14:22:44 -07:00
} ( ) ) ; // END LOCAL_SCOPE