/********************************************************************* * \file LeafPatrol.cs * \author Ryan Wang Nian Jing * \brief Leaf node implementation for patrolling AI * * * \copyright Copyright (c) 2022 DigiPen Institute of Technology. Reproduction or disclosure of this file or its contents without the prior written consent of DigiPen Institute of Technology is prohibited. *********************************************************************/ using SHADE; using SHADE_Scripting.AIBehaviour.BehaviourTree; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; //VARIABLES HERE public partial class LeafPatrol : BehaviourTreeNode { //Waypoints and movement private Transform transform; private List? waypoints; private RigidBody rb; private float patrolSpeed; private float chaseSpeed; private float turningSpeed; private float retreatTimer = 0.0f; private int currentWaypointIndex = 0; private bool retreatState = false; private bool goingForwards = true; //Small delays between waypoints private bool isWaiting = false; private const float waitDuration = 2.0f; private float waitCounter = 0.0f; } //FUNCTIONS HERE public partial class LeafPatrol : BehaviourTreeNode { //Constructor, establish values here //Despite inheriting from BehaviourTreeNode, we don't have children to this //node, and hence we do not need to inherit its constructors public LeafPatrol(string name) : base(name) { currentWaypointIndex = 0; } //When it comes to evaluating, //le this node keep returning RUNNING as it is the last fallback node on tree public override BehaviourTreeNodeStatus Evaluate() { //Debug.LogWarning("LeafPatrol"); onEnter(BehaviourTreeNodeStatus.RUNNING); //Get data if (GetNodeData("transform") == null || GetNodeData("patrolSpeed") == null || GetNodeData("chaseSpeed") == null || GetNodeData("turningSpeed") == null || GetNodeData("rigidBody") == null) { status = BehaviourTreeNodeStatus.FAILURE; onExit(BehaviourTreeNodeStatus.FAILURE); return status; } else { transform = (Transform)GetNodeData("transform"); patrolSpeed = (float)GetNodeData("patrolSpeed"); chaseSpeed = (float)GetNodeData("chaseSpeed"); turningSpeed = (float)GetNodeData("turningSpeed"); rb = (RigidBody)GetNodeData("rigidBody"); } if (GetNodeData("currentWaypointIndex") == null) { SetNodeData("currentWaypointIndex", 0); } if (isWaiting) DelayAtWaypoint(); else MoveToWaypoint(); status = BehaviourTreeNodeStatus.RUNNING; onExit(BehaviourTreeNodeStatus.RUNNING); return status; } //Move and cycle between waypoints private void MoveToWaypoint() { //Debug.Log("MoveToWaypoint"); //Waiting, do not move if (GetNodeData("isWaiting") != null) { //Only wait to change waypoints if not alert if (GetNodeData("isAlert") != null && !(bool)GetNodeData("isAlert")) waitCounter = 0.0f; isWaiting = true; ClearNodeData("isWaiting"); return; } waypoints = (List)GetNodeData("waypoints"); if (waypoints == null) { return; } Vector3 targetPosition = waypoints[currentWaypointIndex].GetComponent().GlobalPosition; //Reach waypoint by X and Z being near enough //Do not consider Y of waypoints yet Vector3 remainingDistance = targetPosition - transform.GlobalPosition; remainingDistance.y = 0.0f; //Reached waypoint, cycle if (remainingDistance.GetSqrMagnitude() < 0.1f) { //If alert, may reverse if (GetNodeData("isAlert") != null && (bool)GetNodeData("isAlert")) { //If alert, may reverse if it's closer to the player if (GetNodeData("playerLastSightedWaypointIndex") != null) { int playerWaypoint = (int)GetNodeData("playerLastSightedWaypointIndex"); int forwardDistance = 0; int backDistance = 0; if (playerWaypoint < currentWaypointIndex) { //Player waypoint is behind current waypoint forwardDistance = playerWaypoint + waypoints.Count() - currentWaypointIndex; backDistance = currentWaypointIndex - playerWaypoint; } else { //Player waypoint is ahead of current waypoint (or same) forwardDistance = playerWaypoint - currentWaypointIndex; backDistance = currentWaypointIndex + waypoints.Count() - playerWaypoint; } if (backDistance < forwardDistance) { //Go backwards goingForwards = false; } else { //Go forward goingForwards = true; } } else { //Fallback if no player waypoint data, go forward goingForwards = true; } } //Cycle waypoints if (goingForwards) { ++currentWaypointIndex; if (currentWaypointIndex >= waypoints.Count()) currentWaypointIndex = 0; } else { --currentWaypointIndex; if (currentWaypointIndex < 0) currentWaypointIndex = waypoints.Count() - 1; } //Write to blackboard SetNodeData("currentWaypointIndex", currentWaypointIndex); //Only wait to change waypoints if not alert if (GetNodeData("isAlert") != null && !(bool)GetNodeData("isAlert")) waitCounter = 0.0f; isWaiting = true; } else if (false /*Physics.OverlapSphere(_selfTransform.position, 0.3f, 1 << 8).Length > 0 && retreatState == false*/) { //TODO //This main segment is to check if the NPC is walking into a solid wall //If they are, do a raycast to find the nearest unobstructed waypoint and head there instead } else //Proceed to waypoint as usual { //Get the difference vector to the waypoint //Debug.Log("Current Waypoint " + waypoints[currentWaypointIndex].x.ToString() + " " + waypoints[currentWaypointIndex].y.ToString() + " " + waypoints[currentWaypointIndex].z.ToString()); //Debug.Log("AI is at " + transform.GlobalPosition.x.ToString() + " " + transform.GlobalPosition.y.ToString() + " " + transform.GlobalPosition.z.ToString()); Vector3 normalisedDifference = targetPosition - transform.GlobalPosition; normalisedDifference.y = 0.0f; //Do not move vertically normalisedDifference /= normalisedDifference.GetMagnitude(); //Debug.Log("Normalised Difference x " + normalisedDifference.x.ToString() + " z " + normalisedDifference.z.ToString()); //Look at the correct direction Quaternion targetRotation = Quaternion.LookRotation(normalisedDifference, new Vector3(0.0f, 1.0f, 0.0f)); transform.LocalRotation = Quaternion.Slerp(transform.LocalRotation, targetRotation, turningSpeed * Time.DeltaTimeF); //transform.GlobalPosition += normalisedDifference * moveSpeed * (float)Time.DeltaTime; //rb.LinearVelocity = normalisedDifference * patrolSpeed; //ORIGINAL INTENDED CODE /*rb.AddForce(new Vector3(normalisedDifference.x, 0.0f, normalisedDifference.z) * movementForceMultiplier); float currentSpeed = MathF.Sqrt(rb.LinearVelocity.x * rb.LinearVelocity.x + rb.LinearVelocity.z * rb.LinearVelocity.z); if (currentSpeed > patrolSpeed) { float adjustmentFactor = patrolSpeed / currentSpeed; Vector3 adjustedVelocity = rb.LinearVelocity; //adjustedVelocity *= adjustmentFactor; adjustedVelocity.x = patrolSpeed; adjustedVelocity.z = patrolSpeed; rb.LinearVelocity = adjustedVelocity; }*/ //TODO delete this when original intended code above works with velocity being limited correctly if (rb != null) { //Move quickly if alert if (GetNodeData("isAlert") != null && (bool)GetNodeData("isAlert")) { //Debug.Log("Fast Patrol"); rb.LinearVelocity = normalisedDifference * chaseSpeed; } else { rb.LinearVelocity = normalisedDifference * patrolSpeed; } //Unalert if AI reaches player nearest if (GetNodeData("currentWaypointIndex") != null && GetNodeData("playerLastSightedWaypointIndex") != null) { if ((int)GetNodeData("currentWaypointIndex") == (int)GetNodeData("playerLastSightedWaypointIndex")) { if (GetNodeData("isAlert") != null && (bool)GetNodeData("isAlert")) { //Debug.Log("Unalert"); Audio.PlaySFXOnce2D("event:/Homeowner/humming"); Audio.StopAllSounds(); Audio.PlayBGMOnce2D("event:/Music/player_undetected"); } SetNodeData("isAlert", false); } } } if (retreatState) { if (retreatTimer < 1.0f) retreatTimer += Time.DeltaTimeF; else { retreatState = false; retreatTimer = 1.0f; } } } } private void DelayAtWaypoint() { waitCounter += Time.DeltaTimeF; if (waitCounter >= waitDuration) isWaiting = false; } }