SHADE_Y3/Assets/Scripts/Gameplay/AIBehaviour/Implemented/LeafNodes/LeafPatrol.cs

272 lines
9.4 KiB
C#

/*********************************************************************
* \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 SHADE_Scripting.Audio;
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<GameObject>? 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<GameObject>)GetNodeData("waypoints");
if (waypoints == null)
{
return;
}
Vector3 targetPosition = waypoints[currentWaypointIndex].GetComponent<Transform>().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");
//AudioHandler.audioClipHandlers["BGMAlert"].Stop(true);
//Audio.PlaySFXOnce2D("event:/Homeowner/humming");
AudioHandler.audioClipHandlers["SFXHumming"].Play();
//AudioHandler.audioClipHandlers["BGMUnalert"].Play();
//AudioHandler.audioClipHandlers["BGMAdaptive"].SetParameter("Detected", 0.0f);
Audio.SetParameter("Detected", 0.0f);
}
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;
}
}