Esempio n. 1
0
        public GameVelocity TryGetLaunchVelocity(CameraInfo cameraInfo)
        {
            GameVelocity result = null;

            if (this.Projectile == null)
            {
                throw new Exception("Trying to launch without a ball");
            }

            // Move the catapult to make sure that it is moved at least once before launch (prevent NaN in launch direction)
            this.Move(cameraInfo);

            var stretchNormalized = DigitExtensions.Clamp((this.stretch - this.properties.MinStretch) / (this.properties.MaxStretch - this.properties.MinStretch), 0.0, 1.0);

            // this is a lerp
            var velocity = (float)(this.properties.MinVelocity * (1d - stretchNormalized) + this.properties.MaxVelocity * stretchNormalized);

            var launchDir = SCNVector3.Normalize(this.pullOrigin.WorldPosition - this.Projectile.WorldPosition);

            if (!launchDir.HasNaN())
            {
                var liftFactor = 0.05f * Math.Abs(1f - SCNVector3.Dot(launchDir, SCNVector3.UnitY)); // used to keep ball in air longer
                var lift       = SCNVector3.UnitY * velocity * liftFactor;

                result = new GameVelocity(this.Projectile.WorldPosition, launchDir * velocity + lift);
            }

            return(result);
        }
        public override void Update(double deltaTimeInSeconds)
        {
            this.currentTime += deltaTimeInSeconds;
            this.CalcCurrentFrameIndex();

            var alpha = (float)((this.currentTime - this.wayPoints[currentFrame].Time) /
                                (this.wayPoints[currentFrame + 1].Time - this.wayPoints[currentFrame].Time));

            alpha = DigitExtensions.Clamp(alpha, 0f, 1f);

            var curPos  = this.wayPoints[currentFrame].Pos;
            var curTan  = this.wayPoints[currentFrame].Tangent;
            var curRot  = this.wayPoints[currentFrame].Rot;
            var nextPos = this.wayPoints[currentFrame + 1].Pos;
            var nextTan = this.wayPoints[currentFrame + 1].Tangent;
            var nextRot = this.wayPoints[currentFrame + 1].Rot;


            var newPosX = this.HermiteCurve(curPos.X, nextPos.X, curTan.X, nextTan.X, alpha);
            var newPosY = this.HermiteCurve(curPos.Y, nextPos.Y, curTan.Y, nextTan.Y, alpha);
            var newPosZ = this.HermiteCurve(curPos.Z, nextPos.Z, curTan.Z, nextTan.Z, alpha);
            var newQuat = SCNQuaternion.Slerp(curRot, nextRot, alpha);

            node.WorldPosition    = new SCNVector3(newPosX, newPosY, newPosZ);
            node.WorldOrientation = newQuat;

            // update child rigid bodies to percolate into physics
            if (this.Entity is GameObject entity)
            {
                if (entity.PhysicsNode?.PhysicsBody != null)
                {
                    entity.PhysicsNode.PhysicsBody.ResetTransform();
                }
            }
        }
        public void Update(CameraInfo cameraInfo)
        {
            if (this.DisplayedVictory)
            {
                // Enlarge victory text before falling down
                this.victoryNode.Opacity = (float)(DigitExtensions.Clamp((GameTime.Time - this.activationStartTime) / FadeTime, 0d, 1d));

                if (GameTime.Time - this.activationStartTime > TimeUntilPhysicsReleased)
                {
                    foreach (var child in this.victoryNode.ChildNodes)
                    {
                        if (child.PhysicsBody != null)
                        {
                            child.PhysicsBody.VelocityFactor        = SCNVector3.One;
                            child.PhysicsBody.AngularVelocityFactor = SCNVector3.One;
                        }
                    }
                }
            }
            else
            {
                // Update win condition
                if (this.DidWin() && this.teamWon != Team.None)
                {
                    this.ActivateVictory();
                }
            }
        }
Esempio n. 4
0
        /// <summary>
        /// Incrementally scales the board by the given amount
        /// </summary>
        public void UpdateScale(float factor)
        {
            // assumes we always scale the same in all 3 dimensions
            var currentScale = this.Scale.X;
            var newScale     = DigitExtensions.Clamp(currentScale * factor, GameBoard.MinimumScale, GameBoard.MaximumScale);

            this.Scale = new SCNVector3(newScale, newScale, newScale);
        }
Esempio n. 5
0
        private void UpdateMusicVolume()
        {
            var volume = UserDefaults.MusicVolume;

            // Map the slider value from 0...1 to a more natural curve:
            this.musicGain = volume * volume;

            foreach (var(_, player) in this.musicPlayers.Where(player => player.Value.State == MusicState.Playing))
            {
                var audioVolume = DigitExtensions.Clamp(this.musicGain * (float)Math.Pow(10f, player.Config.VolumeDB / 20f), 0f, 1f);
                player.AudioPlayer.SetVolume(audioVolume, 0.1d);
            }
        }
Esempio n. 6
0
        public void PlayLaunch(GameVelocity velocity)
        {
            // For the launch, we will play two sounds: a launch twang and a swish
            var length = velocity.Vector.Length;

            length = float.IsNaN(length) ? 0 : DigitExtensions.Clamp(length, 0, 1);

            var launchVel = (byte)(length * 30 + 80);

            this.Play(Note.Launch, launchVel);

            var swishVel = (byte)(length * 63 + 64);

            this.After(() => this.Play(Note.LaunchSwish, swishVel), 1);
        }
Esempio n. 7
0
        public MusicPlayer PlayMusic(string name, double startTime, double fadeIn = 0d)
        {
            var player      = this.MusicPlayer(name);
            var audioPlayer = player.AudioPlayer;

            if (this.CurrentMusicPlayer != null)
            {
                this.StopMusic(this.CurrentMusicPlayer);
            }

            switch (player.State)
            {
            case MusicState.Playing:
                // Nothing to do
                return(player);

            case MusicState.Stopped:
                // Configure the audioPlayer, starting with volume at 0 and then fade in.
                audioPlayer.Volume      = 0;
                audioPlayer.CurrentTime = 0;
                if (player.Config.Loops)
                {
                    audioPlayer.NumberOfLoops = -1;
                }
                else
                {
                    audioPlayer.NumberOfLoops = 0;
                }

                audioPlayer.CurrentTime = startTime;
                audioPlayer.Play();
                break;

            case MusicState.Stopping:
                // Leave it playing. Update the volume and play state below.
                break;
            }

            var volume = DigitExtensions.Clamp(this.musicGain * (float)Math.Pow(10f, player.Config.VolumeDB / 20f), 0f, 1f);

            audioPlayer.SetVolume(volume, fadeIn);

            player.State            = MusicState.Playing;
            this.CurrentMusicPlayer = player;

            return(player);
        }
        public AnimWaypointComponent(SCNNode node, Dictionary <string, object> properties) : base()
        {
            this.node = node;

            if (properties.TryGetValue("speed", out object speed))
            {
                this.speed = (double)speed;
            }

            // find all waypoints
            this.InitWaypoints(this.node);
            this.CalculateTangents();

            // does this animation support random start times?
            if (properties.TryGetValue("random", out object random))
            {
                if (random is bool isRandom && isRandom && this.wayPoints.Any())
                {
                    var last = this.wayPoints.Last();
                    this.currentTime = new Random().NextDouble() * last.Time;
                }
            }

            // do we want to start at a particular percentage along curve?
            if (properties.TryGetValue("phase", out object objectPhase))
            {
                if (objectPhase is double phase && this.wayPoints.Any())
                {
                    var desiredPhase = DigitExtensions.Clamp(phase, 0d, 1d);
                    var last         = this.wayPoints.Last();
                    this.currentTime = desiredPhase * last.Time;
                }
            }

            // do we want to start at a specific point in time?
            if (properties.TryGetValue("offset", out object objectOffset))
            {
                if (objectOffset is double offset && this.wayPoints.Any())
                {
                    var last = this.wayPoints.Last();
                    this.currentTime = offset * last.Time;
                }
            }
        }
Esempio n. 9
0
        public void SimulateStep(double time)
        {
            var minUpdateSeconds = 1f / 120f;
            var maxUpdateSeconds = 1f / 30f;
            var seconds          = DigitExtensions.Clamp((float)(time - this.time), minUpdateSeconds, maxUpdateSeconds);

            // could run multiple iterations if greater than maxUpdateSeconds, but for now just run one

            this.ApplyForces();
            this.AverageVelocities();

            // copy the current state
            var currentTransforms = new List <SimulatedTransform>();

            currentTransforms.AddRange(this.simulatedTransforms);

            // simulate forward
            for (var i = this.BoneInset; i < this.simulatedTransforms.Count - this.BoneInset; i++)
            {
                if (!currentTransforms[i].Dynamic)
                {
                    continue;
                }

                var p = currentTransforms[i].Position;
                var v = currentTransforms[i].Velocity;
                p += v * seconds;

                this.simulatedTransforms[i].Position = p;
            }

            this.PerformPlaneCollision(currentTransforms, seconds);
            this.AlignBones();

            this.time = time;
        }
Esempio n. 10
0
        private SCNVector3 ComputeBallPosition(CameraInfo cameraInfo)
        {
            var cameraRay = cameraInfo.Ray;

            // These should be based on the projectile radius.
            // This affects centering of ball, and can hit near plane of camera
            // This is always centering to one edge of screen independent of screen orient
            // We always want the ball at the bottom of the screen.
            var distancePullToCamera = 0.21f;
            var ballShiftDown        = 0.2f;

            var targetBallPosition = cameraRay.Position + cameraRay.Direction * distancePullToCamera;

            var cameraDown = -SCNVector4.Normalize(cameraInfo.Transform.Column1).Xyz;

            targetBallPosition += cameraDown * ballShiftDown;

            // Clamp to only the valid side
            var pullWorldPosition = this.pullOrigin.WorldPosition;

            if (pullWorldPosition.Z < 0f)
            {
                targetBallPosition.Z = Math.Min(targetBallPosition.Z, pullWorldPosition.Z);
            }
            else
            {
                targetBallPosition.Z = Math.Max(targetBallPosition.Z, pullWorldPosition.Z);
            }

            // Clamp to cone/circular core
            var yDistanceFromPull       = Math.Max(0f, pullWorldPosition.Y - targetBallPosition.Y);
            var minBallDistanceFromPull = 0.5f;
            var pullBlockConeSlope      = 1.0f;
            var pullBlockConeRadius     = yDistanceFromPull / pullBlockConeSlope;
            var pullBlockCoreRadius     = Math.Max(minBallDistanceFromPull, pullBlockConeRadius);

            // if pull is in the core, move it out.
            var pullWorldPositionGrounded  = new SCNVector3(pullWorldPosition.X, 0f, pullWorldPosition.Z);
            var targetPullPositionGrounded = new SCNVector3(targetBallPosition.X, 0f, targetBallPosition.Z);
            var targetInitialToTargetPull  = targetPullPositionGrounded - pullWorldPositionGrounded;

            if (pullBlockCoreRadius > targetInitialToTargetPull.Length)
            {
                var moveOutDirection = SCNVector3.Normalize(targetInitialToTargetPull);
                var newTargetPullPositionGrounded = pullWorldPositionGrounded + moveOutDirection * pullBlockCoreRadius;
                targetBallPosition = new SCNVector3(newTargetPullPositionGrounded.X, targetBallPosition.Y, newTargetPullPositionGrounded.Z);
            }

            // only use the 2d distance, so that user can gauage stretch indepdent of mtch
            var distance2D = targetBallPosition - pullWorldPosition;
            var stretchY   = Math.Abs(distance2D.Y);

            distance2D.Y = 0f;

            var stretchDistance = distance2D.Length;

            this.stretch = DigitExtensions.Clamp((double)stretchDistance, this.properties.MinStretch, this.properties.MaxStretch);

            // clamp a little bit farther than maxStretch
            // can't let the strap move back too far right now
            var clampedStretchDistance = (float)(1.1d * this.properties.MaxStretch);

            if (stretchDistance > clampedStretchDistance)
            {
                targetBallPosition = (clampedStretchDistance / stretchDistance) * (targetBallPosition - pullWorldPosition) + pullWorldPosition;
                stretchDistance    = clampedStretchDistance;
            }

            // Make this optional, not required.  You're often at max stretch.
            // Also have a timer for auto-launch.  This makes it very difficuilt to test
            // storing state in member data
            this.IsPulledTooFar = stretchDistance > (float)(this.properties.MaxStretch) || stretchY > (float)(this.properties.MaxStretch);

            return(targetBallPosition);
        }
Esempio n. 11
0
        public void DoHighlight(bool show, SFXCoordinator sfxCoordinator)
        {
            if (this.HighlightObject != null)
            {
                this.IsHighlighted          = show;
                this.HighlightObject.Hidden = !show;

                if (show)
                {
                    var seconds   = (DateTime.UtcNow - Time1970).TotalSeconds;
                    var intensity = (float)(Math.Sin(seconds.TruncatingRemainder(1) * 3.1415 * 2.0) * 0.2);
                    if (this.HighlightObject.Geometry?.FirstMaterial != null)
                    {
                        var color = new CIColor(this.highlightColor);
                        this.HighlightObject.Geometry.FirstMaterial.Diffuse.Contents = UIColor.FromRGBA((Single)DigitExtensions.Clamp(color.Red + intensity, 0, 1),
                                                                                                        (Single)DigitExtensions.Clamp(color.Green + intensity, 0, 1),
                                                                                                        (Single)DigitExtensions.Clamp(color.Blue + intensity, 0, 1),
                                                                                                        (Single)1);
                    }
                }

                sfxCoordinator?.CatapultDidChangeHighlight(this, show);
            }
        }
Esempio n. 12
0
        public void PlayStretch(Catapult catapult, float stretchDistance, float stretchRate, bool playHaptic)
        {
            var normalizedDistance = DigitExtensions.Clamp((stretchDistance - 0.1f) / 2f, 0f, 1f);

            if (this.isStretchPlaying)
            {
                // Set the stretch distance and rate on the audio
                // player to module the strech sound effect.
                catapult.AudioPlayer.StretchDistance = normalizedDistance;
                catapult.AudioPlayer.StretchRate     = stretchRate;
            }
            else
            {
                catapult.AudioPlayer.StartStretch();
                this.isStretchPlaying = true;
            }

            if (playHaptic)
            {
                double interval;
                if (normalizedDistance >= 0.25 && normalizedDistance < 0.5)
                {
                    interval = 0.5;
                }
                else if (normalizedDistance >= 0.5)
                {
                    interval = 0.25;
                }
                else
                {
                    interval = 1.0;
                }

                if (this.prevStretchDistance.HasValue)
                {
                    var delta = Math.Abs(stretchDistance - this.prevStretchDistance.Value);
                    this.prevStretchDistance = stretchDistance;
                    if (delta < 0.0075f)
                    {
                        return;
                    }
                }
                else
                {
                    this.prevStretchDistance = stretchDistance;
                }

                if (this.timeSinceLastHaptic.HasValue)
                {
                    if ((DateTime.UtcNow - this.timeSinceLastHaptic.Value).TotalSeconds > interval)
                    {
                        this.hapticsGenerator.GenerateImpactFeedback();
                        this.timeSinceLastHaptic = DateTime.UtcNow;
                    }
                }
                else
                {
                    this.hapticsGenerator.GenerateImpactFeedback();
                    this.timeSinceLastHaptic = DateTime.UtcNow;
                }
            }
        }
Esempio n. 13
0
        public CollisionEvent CreateCollisionEvent(float impulse, bool withBall, bool withTable)
        {
            CollisionEvent result = null;

            if (float.IsNaN(impulse) || impulse < this.configuration.MinimumImpulse)
            {
            }
            else
            {
                // Set mod wheel according to the impulse value. This will vary the attack of the sound
                // and make them less repetitive and more dynamic. The sampler patch is set up to play the full
                // sound with modWheel off, and shortened attack with increasing modwheel value. So we invert the
                // normalized range.
                //
                // Also, we want to alter the velocity so that higher impulse means a louder note.

                byte note;
                if (withTable)
                {
                    note = Note.CollisionWithTable;
                }
                else if (withBall)
                {
                    note = Note.CollisionWithBall;
                }
                else
                {
                    note = Note.CollisionWithBlock;
                }

                note = (byte)(note + this.variant[0]);

                // move this variant randomly to another position
                var otherIndex = new Random().Next(variant.Length - 1);// Int(arc4random_uniform(UInt32(variant.count - 1)))

                //variant.swapAt(0, 1 + otherIndex)
                var temp = variant[0];
                variant[0] = variant[1 + otherIndex];
                variant[1 + otherIndex] = temp;

                var normalizedImpulse = DigitExtensions.Clamp((impulse - this.configuration.MinimumImpulse) / (this.configuration.MaximumImpulse - this.configuration.MinimumImpulse),
                                                              0f,
                                                              1f);

                // Once the impulse is normalized to the range 0...1, doing a sqrt
                // on it causes lower values to be higher. This curve was chosen because
                // it sounded better aesthetically.
                normalizedImpulse = (float)Math.Sqrt(normalizedImpulse);

                var rangedImpulse = this.configuration.VelocityMinimum + (this.configuration.VelocityMaximum - this.configuration.VelocityMinimum) * normalizedImpulse;
                var velocity      = (byte)(DigitExtensions.Clamp(rangedImpulse, 0, 127));

                result = new CollisionEvent
                {
                    Note     = note,
                    Velocity = velocity,
                    ModWheel = 1f - normalizedImpulse,
                };
            }

            return(result);
        }
Esempio n. 14
0
        private float StageProgress(double startTime, double endTime)
        {
            var progress = (float)((this.TimeSinceInitialFloatStart - startTime) / (endTime - startTime));

            return(DigitExtensions.Clamp(progress, 0f, 1f));
        }
Esempio n. 15
0
        public void AnimateVortex()
        {
            if (this.Delegate == null)
            {
                throw new Exception("No delegate");
            }

            if (this.vortexCylinder == null)
            {
                throw new Exception("Vortex animation cylinder not set");
            }

            // Vortex shape from animation
            var vortexShape       = this.vortexCylinder.PresentationNode.Scale;
            var vortexHeightDelta = vortexShape.Y - this.lastVortexHeight;

            this.lastVortexHeight = vortexShape.Y;

            var vortexCenterY      = this.vortexCylinder.PresentationNode.WorldPosition.Y;
            var vortexCenterYDelta = vortexCenterY - this.lastVortexCenterY;

            this.lastVortexCenterY = vortexCenterY;

            // Deform shape over time
            var maxOuterRadius      = vortexShape.X;
            var maxInnerRadius      = maxOuterRadius * 0.2f; // 20 % from experiment
            var maxOuterRadiusDelta = maxOuterRadius - this.lastOuterRadius;

            this.lastOuterRadius = maxOuterRadius;

            // Orbital velocity
            var currentFront     = this.vortexCylinder.PresentationNode.WorldFront;
            var orbitalMoveDelta = (currentFront - this.lastFront).Length * maxInnerRadius;

            this.lastFront = currentFront;

            var orbitalVelocityFactor = 5f;
            var orbitalVelocity       = (orbitalMoveDelta / (float)GameTime.DeltaTime) * orbitalVelocityFactor;

            var topBound    = vortexCenterY + vortexShape.Y * 0.5f;
            var bottomBound = vortexCenterY - vortexShape.Y * 0.5f;

            var blockObjects = this.Delegate.AllBlockObjects;
            var up           = SCNVector3.UnitY;

            foreach (var block in blockObjects)
            {
                if (block.PhysicsNode?.PhysicsBody != null)
                {
                    var position           = block.PhysicsNode.PresentationNode.WorldPosition;
                    var positionWithoutY   = new SCNVector3(position.X, 0f, position.Z);
                    var distanceFromCenter = positionWithoutY.Length;
                    var directionToCenter  = -SCNVector3.Normalize(positionWithoutY);

                    // Adjust radius into curve
                    // Equation representing a half radius chord of circle equation
                    var normalizedY  = DigitExtensions.Clamp(position.Y / topBound, 0f, 1f);
                    var radiusFactor = (float)Math.Sqrt(4f - 3f * normalizedY * normalizedY) - 1f;
                    radiusFactor = radiusFactor * 0.8f + 0.2f;
                    var innerRadius = maxInnerRadius * radiusFactor;
                    var outerRadius = maxOuterRadius * radiusFactor;

                    // Cap velocity
                    var maxVelocity = 30f;
                    if (block.PhysicsNode.PhysicsBody.Velocity.Length > maxVelocity)
                    {
                        block.PhysicsNode.PhysicsBody.Velocity = SCNVector3.Normalize(block.PhysicsNode.PhysicsBody.Velocity) * maxVelocity;
                    }

                    var force = SCNVector3.Zero;

                    // Stage specific manipulation
                    var vortexDirection        = SCNVector3.Cross(directionToCenter, up);
                    var speedInVortexDirection = SCNVector3.Dot(block.PhysicsNode.PhysicsBody.Velocity, vortexDirection);

                    // Stable vortex pull
                    var pullForceMagnitude = (speedInVortexDirection * speedInVortexDirection) * (float)(block.PhysicsNode.PhysicsBody.Mass) / distanceFromCenter;
                    force += pullForceMagnitude * directionToCenter;

                    // Pull into outer radius
                    var radialInwardForceMagnitude = RadialSpringConstant * (float)Math.Max(0d, distanceFromCenter - outerRadius);
                    force += radialInwardForceMagnitude * directionToCenter;

                    // Pull away from inner radius
                    var radialOutwardForceMagnitude = RadialSpringConstant * (float)Math.Max(0d, innerRadius - distanceFromCenter);
                    force += -radialOutwardForceMagnitude * directionToCenter;

                    // Vortex velocity adjustment
                    if (distanceFromCenter > innerRadius)
                    {
                        var tangentForceMagnitude = TangentVelocitySpringContant * (speedInVortexDirection - orbitalVelocity);
                        force += -tangentForceMagnitude * vortexDirection * (0.5f + (float)(random.NextDouble() * 1d));
                    }

                    // Random forces/torque
                    force += force.Length * (float)((random.NextDouble() * 2d - 1d) * MaxRandomVortexForce) * up;
                    this.ApplyRandomTorque(block.PhysicsNode.PhysicsBody, MaxRandomVortexTorque);

                    // Top bound pull down
                    var topBoundForceMagnitude = RadialSpringConstant * (float)Math.Max(0d, position.Y - topBound);
                    force += topBoundForceMagnitude * -up;

                    // Bottom bound pull up
                    var bottomBoundForceMagnitude = RadialSpringConstant * (float)Math.Max(0d, bottomBound - position.Y);
                    force += bottomBoundForceMagnitude * up;

                    block.PhysicsNode.PhysicsBody.ApplyForce(force, false);

                    // Scale the vortex
                    // The higher position in the bound, more it should move upward to scale the vortex
                    var normalizedPositionInBoundY = DigitExtensions.Clamp((position.Y - bottomBound) / vortexShape.Y, 0f, 1f);
                    var heightMoveFactor           = Math.Abs(normalizedPositionInBoundY - 0.5f);
                    var newPositionY = position.Y + vortexCenterYDelta + vortexHeightDelta * heightMoveFactor;

                    var positionXZ       = new SCNVector3(position.X, 0f, position.Z);
                    var radialMoveFactor = DigitExtensions.Clamp(distanceFromCenter / outerRadius, 0f, 1f);
                    var newPositionXZ    = positionXZ + maxOuterRadiusDelta * radialMoveFactor * -directionToCenter;

                    block.PhysicsNode.WorldPosition    = new SCNVector3(newPositionXZ.X, newPositionY, newPositionXZ.Z);
                    block.PhysicsNode.WorldOrientation = block.PhysicsNode.PresentationNode.WorldOrientation;
                    block.PhysicsNode.PhysicsBody.ResetTransform();
                }
            }
        }
Esempio n. 16
0
        public void Update(CameraInfo cameraInfo)
        {
            if (this.Delegate == null)
            {
                throw new Exception("No delegate");
            }

            // Do not move the lever after it has been activated
            if (!(this.InteractionToActivate?.IsActivated ?? false))
            {
                if (this.activeSwitch != null)
                {
                    // Lever Pulling
                    var cameraOffset = this.activeSwitch.PullOffset(cameraInfo.Ray.Position - this.startLeverHoldCameraPosition);
                    var cameraMovedZ = cameraOffset.Z;

                    var targetEulerX = this.startLeverEulerX + LeverPullZtoLeverEulerRotation * cameraMovedZ;
                    targetEulerX            = DigitExtensions.Clamp(-LeverMaxEulerX, targetEulerX, LeverMaxEulerX);
                    this.activeSwitch.Angle = targetEulerX;

                    if (targetEulerX <= -LeverMaxEulerX)
                    {
                        // Interaction activation once the switch lever is turned all the way
                        this.InteractionToActivate?.Activate();

                        // Fade out the switches
                        var waitAction = SCNAction.Wait(3f);
                        var fadeAction = SCNAction.FadeOut(3d);

                        foreach (var resetSwitch in this.resetSwitches)
                        {
                            resetSwitch.Base.RunAction(SCNAction.Sequence(new SCNAction[] { waitAction, fadeAction }));
                        }
                        return;
                    }
                    else
                    {
                        // Inform peers of the movement
                        var leverId = this.resetSwitches.IndexOf(this.activeSwitch);
                        if (leverId == -1)
                        {
                            throw new Exception("No lever in array");
                        }

                        this.Delegate.DispatchActionToServer(new GameActionType {
                            LeverMove = new LeverMove(leverId, targetEulerX), Type = GameActionType.GActionType.LeverMove
                        });
                    }
                }
                else
                {
                    // Lever spring back
                    foreach (var lever in this.resetSwitches.Where(lever => lever.Angle < LeverMaxEulerX))
                    {
                        lever.Angle = Math.Min(LeverMaxEulerX, lever.Angle + LeverSpringBackSpeed * (float)GameTime.DeltaTime);
                    }
                }

                // Highlight lever when nearby, otherwise check if we should hide the highlight
                if (this.highlightedSwitch != null)
                {
                    if (!this.highlightedSwitch.ShouldHighlight(cameraInfo.Ray))
                    {
                        this.highlightedSwitch.DoHighlight(false, this.SfxCoordinator);
                        this.highlightedSwitch = null;
                    }
                }
                else
                {
                    foreach (var resetSwitch in this.resetSwitches)
                    {
                        if (resetSwitch.ShouldHighlight(cameraInfo.Ray))
                        {
                            resetSwitch.DoHighlight(true, this.SfxCoordinator);
                            this.highlightedSwitch = resetSwitch;
                        }
                    }
                }
            }
        }