using System.Collections.Generic; using System.Linq; using SanAndreasUnity.Importing.Items.Definitions; using SanAndreasUnity.Importing.Paths; using UnityEngine; using SanAndreasUnity.Utilities; using SanAndreasUnity.Net; namespace SanAndreasUnity.Behaviours { public enum PedAction { Idle = 0, WalkingAround, Chasing, Escaping, Following, } public class PedAI : MonoBehaviour { private static readonly List s_allPedAIs = new List(); public static IReadOnlyList AllPedAIs => s_allPedAIs; private static bool s_subscribedToPedOnDamageEvent = false; public PedAction Action { get; private set; } private Vector3 _moveDestination; /// /// The node where the Ped starts /// public PathNode CurrentNode { get; private set; } /// /// The node the Ped is targeting /// public PathNode TargetNode { get; private set; } /// /// The ped that this ped is chasing /// public Ped TargetPed { get; private set; } public Ped MyPed { get; private set; } public PedestrianType PedestrianType => this.MyPed.PedDef.DefaultType; private void Awake() { this.MyPed = this.GetComponentOrThrow(); if (!s_subscribedToPedOnDamageEvent) { s_subscribedToPedOnDamageEvent = true; Ped.onDamaged += OnPedDamaged; } } private void OnEnable() { s_allPedAIs.Add(this); } private void OnDisable() { s_allPedAIs.Remove(this); } private static void OnPedDamaged(Ped hitPed, DamageInfo dmgInfo, Ped.DamageResult dmgResult) { var hitPedAi = hitPed.GetComponent(); if (null == hitPedAi) return; if (hitPed.PedDef != null && (hitPed.PedDef.DefaultType == PedestrianType.Criminal || hitPed.PedDef.DefaultType == PedestrianType.Cop || hitPed.PedDef.DefaultType.IsGangMember())) { hitPedAi.TargetPed = dmgInfo.GetAttackerPed(); hitPedAi.Action = PedAction.Chasing; } else hitPedAi.Action = PedAction.Escaping; } void Update() { this.MyPed.ResetInput(); if (NetStatus.IsServer) { switch (this.Action) { case PedAction.WalkingAround: if (this.ArrivedAtDestinationNode()) { this.OnArrivedToDestinationNode(); } this.MyPed.IsWalkOn = true; this.MyPed.Movement = (_moveDestination - this.MyPed.transform.position).normalized; this.MyPed.Heading = this.MyPed.Movement; break; case PedAction.Chasing: if (this.TargetPed != null) { Vector3 diff = GetHeadOrTransform(this.TargetPed).position - GetHeadOrTransform(this.MyPed).position; Vector3 dir = diff.normalized; if (diff.magnitude < 10f) { this.MyPed.Heading = dir; this.MyPed.AimDirection = dir; this.MyPed.IsAimOn = true; this.MyPed.IsFireOn = true; } else { this.MyPed.IsRunOn = true; this.MyPed.Movement = dir; this.MyPed.Heading = dir; } } else // The target is dead/disconnected { this.Action = PedAction.WalkingAround; } break; case PedAction.Escaping: if (this.ArrivedAtDestinationNode()) { this.OnArrivedToDestinationNode(); } this.MyPed.IsSprintOn = true; this.MyPed.Movement = (_moveDestination - this.MyPed.transform.position).normalized; this.MyPed.Heading = this.MyPed.Movement; break; case PedAction.Following: this.UpdateFollowing(); break; } } } bool ArrivedAtDestinationNode() { if (Vector2.Distance(this.transform.position.ToVec2WithXAndZ(), this.TargetNode.Position.ToVec2WithXAndZ()) < this.TargetNode.PathWidth / 2f) return true; return false; } void OnArrivedToDestinationNode() { PathNode previousNode = CurrentNode; CurrentNode = TargetNode; TargetNode = GetNextPathNode(previousNode, CurrentNode); Vector2 offset = Random.insideUnitCircle * TargetNode.PathWidth / 2f * 0.9f; _moveDestination = TargetNode.Position + offset.ToVector3XZ(); } void UpdateFollowing() { // follow target ped if (this.TargetPed != null) { Vector3 targetPos = this.TargetPed.transform.position; float currentStoppingDistance = 3f; if (this.TargetPed.IsInVehicleSeat && !this.MyPed.IsInVehicle) { // find a free vehicle seat to enter vehicle var vehicle = this.TargetPed.CurrentVehicle; var closestfreeSeat = Ped.GetFreeSeats (vehicle).Select (sa => new { sa = sa, tr = vehicle.GetSeatTransform (sa) }) .OrderBy (s => s.tr.Distance (this.transform.position)) .FirstOrDefault (); if (closestfreeSeat != null) { // check if it is in range if (closestfreeSeat.tr.Distance (this.transform.position) < this.MyPed.EnterVehicleRadius) { // the seat is in range this.MyPed.EnterVehicle (vehicle, closestfreeSeat.sa); } else { // the seat is not in range // move towards this seat targetPos = closestfreeSeat.tr.position; currentStoppingDistance = 0.1f; } } } else if (!this.TargetPed.IsInVehicle && this.MyPed.IsInVehicleSeat) { // target player is not in vehicle, and ours is // exit the vehicle this.MyPed.ExitVehicle (); } if (this.MyPed.IsInVehicle) return; Vector3 diff = targetPos - this.transform.position; float distance = diff.magnitude; if (distance > currentStoppingDistance) { Vector3 diffDir = diff.normalized; this.MyPed.IsRunOn = true; this.MyPed.Movement = diffDir; this.MyPed.Heading = diffDir; } } else { this.Action = PedAction.Idle; } } public void StartWalkingAround(PathNode pathNode) { this.CurrentNode = pathNode; this.TargetNode = pathNode; this.Action = PedAction.WalkingAround; this.TargetPed = null; } public void StartFollowing(Ped ped) { this.Action = PedAction.Following; this.TargetPed = ped; } public void Recruit(Ped recruiterPed) { if (!this.PedestrianType.IsGangMember() && this.PedestrianType != PedestrianType.Criminal) return; if (this.Action == PedAction.Following) { if (this.TargetPed == recruiterPed) { // unfollow this.TargetPed = null; return; } } else if (this.Action == PedAction.Idle || this.Action == PedAction.WalkingAround) { // start following this.StartFollowing(recruiterPed); } } private static Transform GetHeadOrTransform(Ped ped) { return ped.PlayerModel.Head != null ? ped.PlayerModel.Head : ped.transform; } private static PathNode GetNextPathNode(PathNode previousNode, PathNode currentNode) { var possibilities = new List( NodeReader.GetAllLinkedNodes(currentNode) .Where(_ => !_.Equals(previousNode))); if (possibilities.Count > 0) { return possibilities.RandomElement(); } else { //No possibilities found, returning to previous node return previousNode; } } private void OnDrawGizmosSelected() { Gizmos.color = Color.green; Gizmos.DrawLine(CurrentNode.Position, TargetNode.Position); Gizmos.DrawWireSphere(CurrentNode.Position, CurrentNode.PathWidth / 2f); Gizmos.DrawWireSphere(TargetNode.Position, TargetNode.PathWidth / 2f); Gizmos.color = Color.yellow; NodeReader.GetAllLinkedNodes(TargetNode) .Except(new[] {CurrentNode}) .ForEach(node => { Gizmos.DrawLine(TargetNode.Position, node.Position); Gizmos.DrawWireSphere(node.Position, node.PathWidth / 2f); }); NodeReader.GetAllLinkedNodes(CurrentNode) .Except(new[] {TargetNode}) .ForEach(node => { Gizmos.DrawLine(CurrentNode.Position, node.Position); Gizmos.DrawWireSphere(node.Position, node.PathWidth / 2f); }); } } }