Exemplo n.º 1
0
        /// <summary>
        /// Picks an action for the character to do every tick.
        /// </summary>
        /// <param name="controller">The controller for the character.</param>
        public void ChooseAction(AIController controller)
        {
            Vector3 opponentDistance   = controller.GetOpponentDistance();
            float   horizontalDistance = Mathf.Abs(opponentDistance.x);

            if (horizontalDistance > targetDistance)
            {
                controller.SetRunInDirection(opponentDistance.x);
            }
            else if (horizontalDistance < targetDistance - 1f)
            {
                controller.SetRunInDirection(-opponentDistance.x);
            }
            else
            {
                controller.runSpeed = 0;
            }
            if (controller.runSpeed != 0)
            {
                // Don't chase an opponent off the map.
                RaycastHit hit            = new RaycastHit();
                Vector3    offsetPosition = controller.transform.position;
                offsetPosition.x += controller.runSpeed;
                offsetPosition.y += 0.5f;
                if (!Physics.Raycast(offsetPosition, Vector3.down, out hit, 100, 1 << 13))
                {
                    if (controller.ParkourComponent.Sliding)
                    {
                        controller.SetRunInDirection(-opponentDistance.x);
                    }
                    else
                    {
                        controller.runSpeed = 0;
                    }
                    controller.slide = false;
                }
                else
                {
                    controller.slide = horizontalDistance > targetDistance * 2;
                }
            }
        }
Exemplo n.º 2
0
		/// <summary>
		/// Picks an action for the character to do every tick.
		/// </summary>
		/// <param name="controller">The controller for the character.</param>
		public void ChooseAction(AIController controller)
		{
			Vector3 opponentDistance = controller.GetOpponentDistance();
			float horizontalDistance = Mathf.Abs(opponentDistance.x);
			if (horizontalDistance > targetDistance)
			{
				controller.SetRunInDirection(opponentDistance.x);
			}
			else if (horizontalDistance < targetDistance - 1f)
			{
				controller.SetRunInDirection(-opponentDistance.x);
			}
			else
			{
				controller.runSpeed = 0;
			}
			if (controller.runSpeed != 0) {
				// Don't chase an opponent off the map.
				RaycastHit hit = new RaycastHit();
				Vector3 offsetPosition = controller.transform.position;
				offsetPosition.x += controller.runSpeed;
				offsetPosition.y += 0.5f;
				if (!Physics.Raycast(offsetPosition, Vector3.down, out hit, 100, 1 << 13))
				{
					if (controller.ParkourComponent.Sliding)
					{
						controller.SetRunInDirection(-opponentDistance.x);
					}
					else
					{
						controller.runSpeed = 0;
					}
					controller.slide = false;
				}
				else
				{
					controller.slide = horizontalDistance > targetDistance * 2;
				}
			}
		}
Exemplo n.º 3
0
		public void ChooseAction(AIController controller)
		{
			Vector3 opponentDistance = controller.GetOpponentDistance();
			if (Mathf.Abs(opponentDistance.x) > targetDistance) 
			{
				controller.SetRunInDirection(opponentDistance.x);
				// Don't chase an opponent off the map.
				RaycastHit hit = new RaycastHit();
				Vector3 offsetPosition = controller.transform.position;
				offsetPosition.x += controller.runSpeed;
				if (!Physics.Raycast(offsetPosition, Vector3.down, out hit, 100, 1 << 13)) {
					controller.runSpeed = 0;
				}
			} else {
				controller.runSpeed = 0;
			}
		}
Exemplo n.º 4
0
        public void ChooseAction(AIController controller)
        {
            Vector3 opponentDistance = controller.GetOpponentDistance();

            if (Mathf.Abs(opponentDistance.x) > targetDistance)
            {
                controller.SetRunInDirection(opponentDistance.x);
                // Don't chase an opponent off the map.
                RaycastHit hit            = new RaycastHit();
                Vector3    offsetPosition = controller.transform.position;
                offsetPosition.x += controller.runSpeed;
                if (!Physics.Raycast(offsetPosition, Vector3.down, out hit, 100, 1 << 13))
                {
                    controller.runSpeed = 0;
                }
            }
            else
            {
                controller.runSpeed = 0;
            }
        }
Exemplo n.º 5
0
		/// <summary>
		/// Picks an action for the character to do every tick.
		/// </summary>
		/// <param name="controller">The controller for the character.</param>
		public void ChooseAction(AIController controller)
		{
			if (DEBUGLINES)
			{
				foreach (LedgeNode ledge in ledges)
				{
					foreach (LedgeNode adjacent in ledge.adjacentEdges)
					{
						Debug.DrawLine(ledge.transform.position, adjacent.transform.position, Color.yellow);
					}
				}
			}
			if (target == null) {
				controller.SetRunInDirection(0);
				return;
			}
			Controller targetController = target.GetComponent<Controller>();
			if (targetController != null && targetController.LifeComponent.Health <= 0)
			{
				controller.SetRunInDirection(0);
				return;
			}

			lastSpeed = controller.runSpeed;

			controller.jump = false;
			float currentTargetDistance = targetDistance;
			Vector3 opponentOffset = target.transform.position - controller.transform.position;
			Vector3 targetOffset = opponentOffset;
			float distanceTolerance = targetDistance - 1;
			Vector3 playerCenter = controller.transform.position + Vector3.up * 0.75f;

			RaycastHit under;
			Physics.Raycast(playerCenter, Vector3.down, out under, 30, AIController.LAYERMASK);

			// Check if there is a platform in the way of shooting.
			RaycastHit hit;
			// Find a ledge path to get to the target.
			replanTimer -= Time.deltaTime;
			if (pathTarget != target && currentNode == null)
			{
				replanTimer = 0;
			}
			if (OnSamePlatform(controller.transform, target.transform))
			{
				if (Mathf.Abs(opponentOffset.y) < 5 && (Mathf.Abs(opponentOffset.y) < 0.1f || under.distance < 1))
				{
					currentNode = null;
				}
			}
			else if (replanTimer <= 0)
			{
				replanTimer = REPLANTIME;
				pathTarget = target;
				currentPath = GetPath(GetClosestLedgeNode(controller.transform), GetClosestLedgeNode(target.transform));
				if (currentPath.Count > 0)
				{
					currentPath.Last.Value.YOffset = target.transform.position.y - currentPath.Last.Value.transform.position.y;
					LoadNextLedge();
					currentNode.YOffset = currentNode.transform.position.y - controller.transform.position.y;
					if (currentPath.Count > 0)
					{
						if (GetLedgePlatform(currentPath.First.Value) == GetPlatformUnderneath(controller.transform))
						{
							LoadNextLedge();
						}
						else if (currentPath.Count == 1 && Mathf.Abs(currentNode.transform.position.y - currentPath.First.Value.transform.position.y) < 0.2f)
						{
							currentNode = null;
						}
					}
				}
				else
				{
					currentNode = null;
				}
			}

			if (currentNode != null)
			{
				// Move towards the nearest ledge, jumping if needed.
				Vector3 currentOffset = currentNode.transform.position - controller.transform.position;

				// Go to the next ledge node if possible.
				if (currentNode.YOffset >= 0 && Math.Abs(currentOffset.x) <= LEDGEGRABDISTANCE / 3 && currentOffset.y <= 0  ||
					currentNode.YOffset < 0 && currentOffset.y >= 1.5f)
				{
					LoadNextLedge();
				}
				if (currentNode != null)
				{
					currentOffset = currentNode.transform.position - controller.transform.position;

					currentTargetDistance = 0;
					distanceTolerance = 0.1f;

					if (Mathf.Abs(currentOffset.y) < distanceTolerance)
					{
						currentOffset.y = 0;
					}

					Vector3 platformOffset = currentNode.transform.position - GetLedgePlatform(currentNode).transform.position;

					float ledgeOffset = -0.1f;
					if (currentNode.YOffset > 0 && currentOffset.y > -0.5f ||
						currentNode.YOffset < 0)
					{
						ledgeOffset = LEDGEGRABDISTANCE;
					}
					if (platformOffset.x > 0)
					{
						currentOffset.x += ledgeOffset;
					}
					else
					{
						currentOffset.x -= ledgeOffset;
					}

					controller.jump = currentNode.YOffset >= 0 && Math.Abs(currentOffset.x) <= LEDGEGRABDISTANCE / 2;
					 
					targetOffset = currentOffset;
				}
			}

			if (DEBUGLINES)
			{
				Debug.DrawRay(controller.transform.position, targetOffset, Color.red);
				if (currentNode != null)
				{
					LedgeNode c = currentNode;
					foreach (LedgeNode l in currentPath)
					{
						Debug.DrawLine(c.transform.position, l.transform.position, Color.red);
						c = l;
					}
					Debug.DrawLine(c.transform.position, target.transform.position, Color.red);
				}
			}
				
			LedgeNode closestLedge = null;

			// Check if the AI is falling to its death.
			if (under.collider == null)
			{
				// Find the closest ledge to go to.
				float closestLedgeDistance = Mathf.Infinity;
				foreach (LedgeNode ledge in ledges)
				{
                    if (ledge != null)
                    {
                        float currentDistance = Mathf.Abs(ledge.transform.position.x - controller.transform.position.x);
                        if (currentDistance < closestLedgeDistance && ledge.transform.position.y < controller.transform.position.y + 1)
                        {
                            closestLedge = ledge;
                            closestLedgeDistance = currentDistance;
                        }
                    }
				}
				bool awayFromLedge = false;
				if (closestLedge == null)
				{
					controller.SetRunInDirection(-controller.transform.position.x);
				}
				else {
					float ledgeOffsetX = closestLedge.transform.position.x - controller.transform.position.x;
					if (Mathf.Abs(ledgeOffsetX) > LEDGEGRABDISTANCE)
					{
						controller.SetRunInDirection(ledgeOffsetX);
						awayFromLedge = true;
					}
				}
				controller.jump = true;
				if (awayFromLedge)
				{
					return;
				}
			}

			// Move towards the opponent.
			float horizontalDistance = Mathf.Abs(targetOffset.x);
			if (horizontalDistance > currentTargetDistance)
			{
				controller.SetRunInDirection(targetOffset.x);
			}
			else if (horizontalDistance < currentTargetDistance - distanceTolerance)
			{
				controller.SetRunInDirection(-targetOffset.x);
			}
			else if (opponentOffset == targetOffset && under.collider != null && (controller.ParkourComponent.FacingRight ^ opponentOffset.x > 0))
			{
				controller.ParkourComponent.FacingRight = opponentOffset.x > 0;
			}
			else
			{
				controller.runSpeed = 0;
			}
			if (controller.runSpeed != 0)
			{
				// Don't chase an opponent off the map.
				Vector3 offsetPosition = controller.transform.position;
				offsetPosition.x += controller.runSpeed / 3;
				offsetPosition.y += 0.5f;
				Vector3 offsetPosition3 = offsetPosition;
				offsetPosition3.x += controller.runSpeed;
				if (!Physics.Raycast(offsetPosition, Vector3.down, out hit, 30, AIController.LAYERMASK) &&
					!Physics.Raycast(offsetPosition3, Vector3.down, out hit, 30, AIController.LAYERMASK))
				{
					if (controller.ParkourComponent.Sliding)
					{
						controller.SetRunInDirection(-opponentOffset.x);
					}
					else if (closestLedge == null)
					{
						controller.runSpeed = 0;
					}
					controller.slide = false;
				}
				else
				{
					// Slide if the opponent is far enough away for sliding to be useful.
					controller.ParkourComponent.FacingRight = opponentOffset.x > 0;
					controller.slide = horizontalDistance > 10 && currentNode == null && OnSamePlatform(controller.transform, target.transform);
				}
			}

			if (controller.runSpeed == 0 && Mathf.Abs(opponentOffset.x) < 1 && opponentOffset.y < 0 && target.GetComponent<Controller>() && controller.GetComponent<Rigidbody>().velocity.y <= Mathf.Epsilon)
			{
				// Don't sit on top of the opponent.
				controller.SetRunInDirection(-controller.transform.position.x);
			}

			if (controller.runSpeed > 0 && lastSpeed < 0 || controller.runSpeed < 0 && lastSpeed > 0)
			{
				// Check if the AI turned very recently to avoid thrashing.
				turnTimer -= Time.deltaTime;
				if (turnTimer <= 0) {
					turnTimer = TURNCOOLDOWN;
				} else {
					controller.runSpeed = 0;
				}
			}

			// Jump to reach some tokens.
			if (targetDistance == 0 && controller.runSpeed == 0 && target.GetComponent<ArrowToken>() && opponentOffset == targetOffset) {
				controller.jump = true;
			}

			controller.fastFall = targetOffset.y < -1f;
		}
Exemplo n.º 6
0
        /// <summary>
        /// Picks an action for the character to do every tick.
        /// </summary>
        /// <param name="controller">The controller for the character.</param>
        public void ChooseAction(AIController controller)
        {
            if (DEBUGLINES)
            {
                foreach (LedgeNode ledge in ledges)
                {
                    foreach (LedgeNode adjacent in ledge.adjacentEdges)
                    {
                        Debug.DrawLine(ledge.transform.position, adjacent.transform.position, Color.yellow);
                    }
                }
            }
            if (target == null)
            {
                controller.SetRunInDirection(0);
                return;
            }
            Controller targetController = target.GetComponent <Controller>();

            if (targetController != null && targetController.LifeComponent.Health <= 0)
            {
                controller.SetRunInDirection(0);
                return;
            }

            lastSpeed = controller.runSpeed;

            controller.jump = false;
            float   currentTargetDistance = targetDistance;
            Vector3 opponentOffset        = target.transform.position - controller.transform.position;
            Vector3 targetOffset          = opponentOffset;
            float   distanceTolerance     = targetDistance - 1;
            Vector3 playerCenter          = controller.transform.position + Vector3.up * 0.75f;

            RaycastHit under;

            Physics.Raycast(playerCenter, Vector3.down, out under, 30, AIController.LAYERMASK);

            // Check if there is a platform in the way of shooting.
            RaycastHit hit;

            // Find a ledge path to get to the target.
            replanTimer -= Time.deltaTime;
            if (pathTarget != target && currentNode == null)
            {
                replanTimer = 0;
            }
            if (OnSamePlatform(controller.transform, target.transform))
            {
                if (Mathf.Abs(opponentOffset.y) < 5 && (Mathf.Abs(opponentOffset.y) < 0.1f || under.distance < 1))
                {
                    currentNode = null;
                }
            }
            else if (replanTimer <= 0)
            {
                replanTimer = REPLANTIME;
                pathTarget  = target;
                currentPath = GetPath(GetClosestLedgeNode(controller.transform), GetClosestLedgeNode(target.transform));
                if (currentPath.Count > 0)
                {
                    currentPath.Last.Value.YOffset = target.transform.position.y - currentPath.Last.Value.transform.position.y;
                    LoadNextLedge();
                    currentNode.YOffset = currentNode.transform.position.y - controller.transform.position.y;
                    if (currentPath.Count > 0)
                    {
                        if (GetLedgePlatform(currentPath.First.Value) == GetPlatformUnderneath(controller.transform))
                        {
                            LoadNextLedge();
                        }
                        else if (currentPath.Count == 1 && Mathf.Abs(currentNode.transform.position.y - currentPath.First.Value.transform.position.y) < 0.2f)
                        {
                            currentNode = null;
                        }
                    }
                }
                else
                {
                    currentNode = null;
                }
            }

            if (currentNode != null)
            {
                // Move towards the nearest ledge, jumping if needed.
                Vector3 currentOffset = currentNode.transform.position - controller.transform.position;

                // Go to the next ledge node if possible.
                if (currentNode.YOffset >= 0 && Math.Abs(currentOffset.x) <= LEDGEGRABDISTANCE / 3 && currentOffset.y <= 0 ||
                    currentNode.YOffset < 0 && currentOffset.y >= 1.5f)
                {
                    LoadNextLedge();
                }
                if (currentNode != null)
                {
                    currentOffset = currentNode.transform.position - controller.transform.position;

                    currentTargetDistance = 0;
                    distanceTolerance     = 0.1f;

                    if (Mathf.Abs(currentOffset.y) < distanceTolerance)
                    {
                        currentOffset.y = 0;
                    }

                    Vector3 platformOffset = currentNode.transform.position - GetLedgePlatform(currentNode).transform.position;

                    float ledgeOffset = -0.1f;
                    if (currentNode.YOffset > 0 && currentOffset.y > -0.5f ||
                        currentNode.YOffset < 0)
                    {
                        ledgeOffset = LEDGEGRABDISTANCE;
                    }
                    if (platformOffset.x > 0)
                    {
                        currentOffset.x += ledgeOffset;
                    }
                    else
                    {
                        currentOffset.x -= ledgeOffset;
                    }

                    controller.jump = currentNode.YOffset >= 0 && Math.Abs(currentOffset.x) <= LEDGEGRABDISTANCE / 2;

                    targetOffset = currentOffset;
                }
            }

            if (DEBUGLINES)
            {
                Debug.DrawRay(controller.transform.position, targetOffset, Color.red);
                if (currentNode != null)
                {
                    LedgeNode c = currentNode;
                    foreach (LedgeNode l in currentPath)
                    {
                        Debug.DrawLine(c.transform.position, l.transform.position, Color.red);
                        c = l;
                    }
                    Debug.DrawLine(c.transform.position, target.transform.position, Color.red);
                }
            }

            LedgeNode closestLedge = null;

            // Check if the AI is falling to its death.
            if (under.collider == null)
            {
                // Find the closest ledge to go to.
                float closestLedgeDistance = Mathf.Infinity;
                foreach (LedgeNode ledge in ledges)
                {
                    if (ledge != null)
                    {
                        float currentDistance = Mathf.Abs(ledge.transform.position.x - controller.transform.position.x);
                        if (currentDistance < closestLedgeDistance && ledge.transform.position.y < controller.transform.position.y + 1)
                        {
                            closestLedge         = ledge;
                            closestLedgeDistance = currentDistance;
                        }
                    }
                }
                bool awayFromLedge = false;
                if (closestLedge == null)
                {
                    controller.SetRunInDirection(-controller.transform.position.x);
                }
                else
                {
                    float ledgeOffsetX = closestLedge.transform.position.x - controller.transform.position.x;
                    if (Mathf.Abs(ledgeOffsetX) > LEDGEGRABDISTANCE)
                    {
                        controller.SetRunInDirection(ledgeOffsetX);
                        awayFromLedge = true;
                    }
                }
                controller.jump = true;
                if (awayFromLedge)
                {
                    return;
                }
            }

            // Move towards the opponent.
            float horizontalDistance = Mathf.Abs(targetOffset.x);

            if (horizontalDistance > currentTargetDistance)
            {
                controller.SetRunInDirection(targetOffset.x);
            }
            else if (horizontalDistance < currentTargetDistance - distanceTolerance)
            {
                controller.SetRunInDirection(-targetOffset.x);
            }
            else if (opponentOffset == targetOffset && under.collider != null && (controller.ParkourComponent.FacingRight ^ opponentOffset.x > 0))
            {
                controller.ParkourComponent.FacingRight = opponentOffset.x > 0;
            }
            else
            {
                controller.runSpeed = 0;
            }
            if (controller.runSpeed != 0)
            {
                // Don't chase an opponent off the map.
                Vector3 offsetPosition = controller.transform.position;
                offsetPosition.x += controller.runSpeed / 3;
                offsetPosition.y += 0.5f;
                Vector3 offsetPosition3 = offsetPosition;
                offsetPosition3.x += controller.runSpeed;
                if (!Physics.Raycast(offsetPosition, Vector3.down, out hit, 30, AIController.LAYERMASK) &&
                    !Physics.Raycast(offsetPosition3, Vector3.down, out hit, 30, AIController.LAYERMASK))
                {
                    if (controller.ParkourComponent.Sliding)
                    {
                        controller.SetRunInDirection(-opponentOffset.x);
                    }
                    else if (closestLedge == null)
                    {
                        controller.runSpeed = 0;
                    }
                    controller.slide = false;
                }
                else
                {
                    // Slide if the opponent is far enough away for sliding to be useful.
                    controller.ParkourComponent.FacingRight = opponentOffset.x > 0;
                    controller.slide = horizontalDistance > 10 && currentNode == null && OnSamePlatform(controller.transform, target.transform);
                }
            }

            if (controller.runSpeed == 0 && Mathf.Abs(opponentOffset.x) < 1 && opponentOffset.y < 0 && target.GetComponent <Controller>() && controller.GetComponent <Rigidbody>().velocity.y <= Mathf.Epsilon)
            {
                // Don't sit on top of the opponent.
                controller.SetRunInDirection(-controller.transform.position.x);
            }

            if (controller.runSpeed > 0 && lastSpeed < 0 || controller.runSpeed < 0 && lastSpeed > 0)
            {
                // Check if the AI turned very recently to avoid thrashing.
                turnTimer -= Time.deltaTime;
                if (turnTimer <= 0)
                {
                    turnTimer = TURNCOOLDOWN;
                }
                else
                {
                    controller.runSpeed = 0;
                }
            }

            // Jump to reach some tokens.
            if (targetDistance == 0 && controller.runSpeed == 0 && target.GetComponent <ArrowToken>() && opponentOffset == targetOffset)
            {
                controller.jump = true;
            }

            controller.fastFall = targetOffset.y < -1f;
        }
Exemplo n.º 7
0
		/// <summary>
		/// Picks an action for the character to do every tick.
		/// </summary>
		/// <param name="controller">The controller for the character.</param>
		public void ChooseAction(AIController controller)
		{
			if (target == null) {
				return;
			}

			lastSpeed = controller.runSpeed;

			controller.jump = false;
			float currentTargetDistance = targetDistance;
			Vector3 opponentOffset = target.transform.position - controller.transform.position;
			Vector3 targetOffset = opponentOffset;
			float distanceTolerance = 1f;


			// Check if there is a platform in the way of shooting.
			RaycastHit hit;
			controller.HasClearShot(opponentOffset, out hit);

			Transform blockingLedge = null;
			if (hit.collider != null)
			{
				// If an obstacle is in the way, move around it.
				float closestDistance = Mathf.Infinity;
				// Find ledges on the obstructing platform.
				BoxCollider[] children = hit.collider.GetComponentsInChildren<BoxCollider>();
				bool foundBetween = false;
				foreach (BoxCollider child in children)
				{
					if (child.tag == "Ledge")
					{
						Vector3 ledgeOffset = child.transform.position - controller.transform.position;
						if (Mathf.Sign(ledgeOffset.y) == Mathf.Sign(targetOffset.y)) {
							// Look for the closest ledge to grab or fall from.
							float currentDistance = Mathf.Abs(child.transform.position.x - controller.transform.position.x);
							bool between = BetweenX(child.transform, controller.transform, target.transform);
							if (!(foundBetween && !between) && currentDistance < closestDistance || !foundBetween && between)
							{
								foundBetween = foundBetween || between;
								// Make sure the edge isn't off the side of the map.
								float edgeMultiplier = 3;
								if (Physics.Raycast(child.transform.position + Vector3.down * 0.5f + Vector3.left * edgeMultiplier, Vector3.down, 30, AIController.LAYERMASK) ||
									Physics.Raycast(child.transform.position + Vector3.down * 0.5f + Vector3.right * edgeMultiplier, Vector3.down, 30, AIController.LAYERMASK))
								{
									// Don't target ledges that have already been jumped over.
									if (currentDistance > LEDGEGRABDISTANCE || child.transform.position.y >= controller.transform.position.y)
									{
										blockingLedge = child.transform;
										closestDistance = currentDistance;
									}
								}
							}
						}
					}
				}
			}

			Transform gapLedge = null;
			RaycastHit under;
			Physics.Raycast(controller.transform.position + Vector3.up * 0.5f, Vector3.down, out under, 30, AIController.LAYERMASK);
			if (hit.collider == null)
			{
				// If the ranger and its target are not on the same platform, go to a nearby ledge.
				RaycastHit underTarget;
				Physics.Raycast(target.transform.position + Vector3.up * 0.5f, Vector3.down, out underTarget, 30, AIController.LAYERMASK);
				if (under.collider != null && underTarget.collider != null && under.collider.gameObject != underTarget.collider.gameObject)
				{
					float closestLedgeDistance = Mathf.Infinity;
					foreach (GameObject ledge in ledges)
					{
						float currentDistance = Mathf.Abs(ledge.transform.position.x - controller.transform.position.x);
						if (currentDistance < closestLedgeDistance && ledge.transform.position.y > controller.transform.position.y + 1 &&
							BetweenX(ledge.transform, controller.transform, target.transform))
						{
							gapLedge = ledge.transform;
							closestLedgeDistance = currentDistance;
						}
					}
				}
			}

			Transform closestLedge;
			if (blockingLedge == null)
			{
				closestLedge = gapLedge;
			}
			else if (gapLedge == null)
			{
				closestLedge = blockingLedge;
			}
			else
			{
				if (Vector3.Distance(blockingLedge.position, controller.transform.position) <= Vector3.Distance(gapLedge.position, controller.transform.position))
				{
					closestLedge = blockingLedge;
				}
				else
				{
					closestLedge = gapLedge;
				}
			}

			if (closestLedge != null)
			{
				// Move towards the nearest ledge, jumping if needed.
				Vector3 closestVector = closestLedge.position - controller.transform.position;
				if (closestLedge.position.x - closestLedge.parent.position.x > 0)
				{
					closestVector.x += LEDGEGRABDISTANCE;
				}
				else
				{
					closestVector.x -= LEDGEGRABDISTANCE;
				}
				if (Math.Abs(closestVector.x) < 1f)
				{
					controller.jump = opponentOffset.y > 0 || gapLedge != null;
				}
				else
				{
					controller.jump = false;
				}
				currentTargetDistance = 0;
				distanceTolerance = 0.1f;
				targetOffset = closestVector;
			}
			Debug.DrawRay(controller.transform.position, targetOffset);

			// Check if the AI is falling to its death.
			if (under.collider == null)
			{
				// Find the closest ledge to go to.
				closestLedge = null;
				float closestLedgeDistance = Mathf.Infinity;
				foreach (GameObject ledge in ledges)
				{
					float currentDistance = Mathf.Abs(ledge.transform.position.x - controller.transform.position.x);
					if (currentDistance < closestLedgeDistance && ledge.transform.position.y < controller.transform.position.y + 1)
					{
						closestLedge = ledge.transform;
						closestLedgeDistance = currentDistance;
					}
				}
				bool awayFromLedge = false;
				if (closestLedge == null)
				{
					controller.SetRunInDirection(-controller.transform.position.x);
				}
				else {
					float ledgeOffsetX = closestLedge.position.x - controller.transform.position.x;
					if (Mathf.Abs(ledgeOffsetX) > LEDGEGRABDISTANCE)
					{
						controller.SetRunInDirection(ledgeOffsetX);
						awayFromLedge = true;
					}
				}
				controller.jump = true;
				if (awayFromLedge)
				{
					return;
				}
			}

			if (currentTargetDistance > 0 && targetOffset.y < -1 && (closestLedge != null || Mathf.Abs(opponentOffset.x) > 1))
			{
				// Move onto a platform if a ledge was just negotiated.
				currentTargetDistance = 0;
			}

			// Move towards the opponent.
			float horizontalDistance = Mathf.Abs(targetOffset.x);
			if (horizontalDistance > currentTargetDistance)
			{
				controller.SetRunInDirection(targetOffset.x);
			}
			else if (horizontalDistance < currentTargetDistance - distanceTolerance)
			{
				controller.SetRunInDirection(-targetOffset.x);
			}
			else
			{
				controller.runSpeed = 0;
			}
			if (controller.runSpeed != 0)
			{
				// Don't chase an opponent off the map.
				Vector3 offsetPosition = controller.transform.position;
				offsetPosition.x += controller.runSpeed;
				offsetPosition.y += 0.5f;
				Vector3 offsetPosition3 = offsetPosition;
				offsetPosition3.x += controller.runSpeed * 2;
				if (!Physics.Raycast(offsetPosition, Vector3.down, out hit, 30, AIController.LAYERMASK) &&
					!Physics.Raycast(offsetPosition3, Vector3.down, out hit, 30, AIController.LAYERMASK))
				{
					if (controller.ParkourComponent.Sliding)
					{
						controller.SetRunInDirection(-opponentOffset.x);
					}
					else if (closestLedge == null)
					{
						controller.runSpeed = 0;
					}
					controller.slide = false;
				}
				else
				{
					// Slide if the opponent is far enough away for sliding to be useful.
					controller.slide = horizontalDistance > targetDistance * 2;
				}
			}

			if (controller.runSpeed == 0 && Mathf.Abs(opponentOffset.x) < 1 && opponentOffset.y < 0 && target.GetComponent<Controller>() && controller.GetComponent<Rigidbody>().velocity.y <= Mathf.Epsilon)
			{
				// Don't sit on top of the opponent.
				controller.SetRunInDirection(-controller.transform.position.x);
			}

			if (controller.runSpeed > 0 && lastSpeed < 0 || controller.runSpeed < 0 && lastSpeed > 0)
			{
				// Check if the AI turned very recently to avoid thrashing.
				if (turnTimer-- <= 0) {
					turnTimer = TURNCOOLDOWN;
				} else {
					controller.runSpeed = 0;
				}
			}

			// Jump to reach some tokens.
			if (targetDistance == 0 && controller.runSpeed == 0) {
				controller.jump = true;
			}
		}