/********************************************************************* * \file LeafSearch.cs * \author Ryan Wang Nian Jing * \brief Leaf node implementation for AI searching for player * * * \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.Runtime.CompilerServices; using System.Text; using System.Threading.Tasks; //VARIABLES HERE public partial class LeafSearch : BehaviourTreeNode { private Transform transform; private Vector3 eyeOffset; private float sightDistance; private GameObject? player; //To be searched for and marked } //FUNCTIONS HERE public partial class LeafSearch : BehaviourTreeNode { public LeafSearch(string name) : base(name) { //Debug.Log("LeafSearch ctor"); } //Helper, find the nearest unobstructed waypoint to return to when chase is over public void reevaluateWaypoint() { List waypoints = (List)GetNodeData("waypoints"); if (waypoints == null) { SetNodeData("currentWaypointIndex", 0); return; } int nearestWaypointIndex = 0; for (int i = 0; i < waypoints.Count; ++i) { if ((transform.GlobalPosition - waypoints[i].GetComponent().GlobalPosition).GetSqrMagnitude() < (transform.GlobalPosition - waypoints[nearestWaypointIndex].GetComponent().GlobalPosition).GetSqrMagnitude()) { nearestWaypointIndex = i; } } SetNodeData("currentWaypointIndex", nearestWaypointIndex); } //Helper for handling being alert //Helper for handling stopping of chases private void handleChaseStop() { if (GetNodeData("isAlert") != null && (bool)GetNodeData("isAlert") == true) { Audio.PlaySFXOnce2D("event:/Homeowner/humming"); reevaluateWaypoint(); } SetNodeData("isAlert", false); } public override BehaviourTreeNodeStatus Evaluate() { //Debug.LogWarning("LeafSearch"); onEnter(BehaviourTreeNodeStatus.RUNNING); //Get data if (GetNodeData("transform") == null || GetNodeData("eyeOffset") == null || GetNodeData("sightDistance") == null) { status = BehaviourTreeNodeStatus.FAILURE; onExit(BehaviourTreeNodeStatus.FAILURE); return status; } else { transform = (Transform)GetNodeData("transform"); eyeOffset = (Vector3)GetNodeData("eyeOffset"); sightDistance = (float)GetNodeData("sightDistance"); } //Search for player player = GameObject.Find("Player"); //Automatically fail if no player is found if (player == null) { SetNodeData("isAlert", false); status = BehaviourTreeNodeStatus.FAILURE; onExit(BehaviourTreeNodeStatus.FAILURE); return status; } else { //Fail if unable to find a player //Get player's transform Transform plrT = player.GetValueOrDefault().GetComponent(); //DELETE THIS //Debug.Log("X " + MathF.Sin(transform.LocalEulerAngles.y).ToString() + " Z " + MathF.Cos(transform.LocalEulerAngles.y).ToString()); //Debug.Log("Looking at: " + transform.LocalRotation.y.ToString() + " To player is: " + temporary.ToString()); //Debug.Log("Look difference is: " + (transform.LocalRotation.y - differenceDirection.y).ToString()); //Debug.Log("Dot: " + Quaternion.Dot(differenceDirection, transform.GlobalRotation)); //Fail if too far from vision range if ((plrT.GlobalPosition - transform.GlobalPosition).GetMagnitude() > sightDistance) { //Debug.Log("Failure: Too far"); //handleChaseStop(); status = BehaviourTreeNodeStatus.FAILURE; onExit(BehaviourTreeNodeStatus.FAILURE); return status; } //Fail if player is out of FOV //TODO currently a simple dot product against negative is done, this makes it essentially be a semicircle in front at which AI can see Vector3 difference = plrT.GlobalPosition - transform.GlobalPosition; difference.y = 0.0f; //Disregard Y axis Vector3 lookDirection = new Vector3(MathF.Sin(transform.LocalEulerAngles.y), 0.0f, MathF.Cos(transform.LocalEulerAngles.y)); //Debug.Log("Dot: " + Vector3.Dot(difference, lookDirection)); if (Vector3.Dot(difference, lookDirection) < 0.0f) { //Debug.Log("Failure: Out of FOV"); //handleChaseStop(); status = BehaviourTreeNodeStatus.FAILURE; onExit(BehaviourTreeNodeStatus.FAILURE); return status; } //LocalRotation is between -1 and 1, which are essentially the same. //0 and -1/1 are 180 deg apart //Quaternion differenceDirection = Quaternion.FromToRotation(Vector3.Forward, plrT.GlobalPosition - transform.GlobalPosition); //Debug.Log("Looking at: " + transform.LocalRotation.y.ToString() + " To player is: " + differenceDirection.y.ToString()); //Draw a ray, succeed if ray is unobstructed Vector3 eyePosition = transform.GlobalPosition + eyeOffset; BoxCollider playerCollider = player.GetValueOrDefault().GetComponent().GetCollisionShape(0); if (playerCollider == null) { //Debug.Log("Failure: Player has no collider"); status = BehaviourTreeNodeStatus.FAILURE; onExit(BehaviourTreeNodeStatus.FAILURE); return status; } //Ray destination to target the centre of the player's collider instead of transform position //Since transform position is often the raccoon's base and the ray needs to hit somewhere higher to be more reliable Vector3 rayDestination = plrT.GlobalPosition + plrT.GlobalScale * playerCollider.PositionOffset; Ray sightRay = new Ray(eyePosition, rayDestination - eyePosition); RaycastHit sightRayHit = Physics.Raycast(sightRay); //As of November 2022, RaycastHit contains only the FIRST object hit by //the ray in the Other GameObject data member //Diren may likely add ALL objects hit by the ray over December if (sightRayHit.Hit && sightRayHit.Other != player) { //TODO sometimes the ray doesn't hit the player even if he's in plain sight because the ray hits the floor the player is on instead??? //Debug.Log("Failure: Ray hit obstacle named " + sightRayHit.Other.GetValueOrDefault().Name + " ID" + sightRayHit.Other.GetValueOrDefault().EntityId); //handleChaseStop(); status = BehaviourTreeNodeStatus.FAILURE; onExit(BehaviourTreeNodeStatus.FAILURE); return status; } else if (sightRayHit.Hit && sightRayHit.Other == player) { //Debug.Log("Ray hit player"); } //All checks for now succeeded //Debug.Log("Success: Homeowner has sighted player"); //Write player's transform into the blackboard SetNodeData("target", plrT); if (GetNodeData("isAlert") == null) { SetNodeData("isAlert", true); Audio.PlaySFXOnce2D("event:/Homeowner/homeowner_detect_raccoon"); Audio.PlaySFXOnce2D("event:/Music/stingers/player_detected"); Audio.StopAllSounds(); Audio.PlayBGMOnce2D("event:/Music/player_detected"); } else { if (GetNodeData("isAlert") != null && (bool)GetNodeData("isAlert") == false) { Audio.PlaySFXOnce2D("event:/Homeowner/homeowner_detect_raccoon"); Audio.PlaySFXOnce2D("event:/Music/stingers/player_detected"); Audio.StopAllSounds(); Audio.PlayBGMOnce2D("event:/Music/player_detected"); } SetNodeData("isAlert", true); } status = BehaviourTreeNodeStatus.SUCCESS; onExit(BehaviourTreeNodeStatus.SUCCESS); return status; } } }