FaceoffManager.cs 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461
  1. using UnityEngine;
  2. using System.Collections;
  3. using System.Collections.Generic;
  4. using System.Linq;
  5. using UnityEngine.InputSystem;
  6. public class FaceoffManager : MonoBehaviour
  7. {
  8. [Header("References")]
  9. [SerializeField] private PuckController puck;
  10. [Header("Faceoff Spots")]
  11. [SerializeField] private Transform centerIceFaceoffSpot;
  12. [SerializeField] private Transform homeZoneLeftFaceoffSpot;
  13. [SerializeField] private Transform homeZoneRightFaceoffSpot;
  14. [SerializeField] private Transform awayZoneLeftFaceoffSpot;
  15. [SerializeField] private Transform awayZoneRightFaceoffSpot;
  16. [SerializeField] private Transform homeZoneBlueLineLeftFaceoffSpot;
  17. [SerializeField] private Transform homeZoneBlueLineRightFaceoffSpot;
  18. [SerializeField] private Transform awayZoneBlueLineLeftFaceoffSpot;
  19. [SerializeField] private Transform awayZoneBlueLineRightFaceoffSpot;
  20. private Transform currentFaceoffSpot;
  21. [Header("Faceoff Settings")]
  22. [SerializeField] private float faceoffDuration = 2.0f;
  23. [SerializeField] private float struggleDuration = 3.0f;
  24. [SerializeField] private float struggleChance = 0.3f;
  25. [SerializeField] private float winThreshold = 0.7f;
  26. [Header("Puck Launch Settings")]
  27. [SerializeField] private float passForce = 8f; // Force to send puck to teammate
  28. [SerializeField] private float struggleLaunchForce = 5f; // Force when puck is loose
  29. private PlayerController homeCenterPlayer;
  30. private PlayerController awayCenterPlayer;
  31. private List<PlayerController> homeTeam = new List<PlayerController>();
  32. private List<PlayerController> awayTeam = new List<PlayerController>();
  33. private bool faceoffInProgress = false;
  34. private FaceoffOutcome currentOutcome;
  35. // Public property to check if faceoff is in progress
  36. public bool IsFaceoffInProgress => faceoffInProgress;
  37. public enum FaceoffOutcome
  38. {
  39. None,
  40. HomeWin,
  41. AwayWin,
  42. Struggle
  43. }
  44. void Start()
  45. {
  46. FindPlayers();
  47. }
  48. void FindPlayers()
  49. {
  50. PlayerController[] allPlayers = FindObjectsByType<PlayerController>(FindObjectsSortMode.None);
  51. homeTeam.Clear();
  52. awayTeam.Clear();
  53. foreach (var player in allPlayers)
  54. {
  55. if (player.CompareTag("HomeTeam"))
  56. {
  57. homeTeam.Add(player);
  58. if (player.stats.position == PlayerPosition.C)
  59. {
  60. homeCenterPlayer = player;
  61. }
  62. }
  63. else if (player.CompareTag("AwayTeam"))
  64. {
  65. awayTeam.Add(player);
  66. if (player.stats.position == PlayerPosition.C)
  67. {
  68. awayCenterPlayer = player;
  69. }
  70. }
  71. }
  72. Debug.Log($"Found {homeTeam.Count} home players and {awayTeam.Count} away players");
  73. }
  74. public void StartFaceoffAt(Transform faceoffSpot)
  75. {
  76. if (faceoffSpot == null)
  77. {
  78. Debug.LogWarning("Faceoff spot is not assigned!");
  79. return;
  80. }
  81. currentFaceoffSpot = faceoffSpot;
  82. StartFaceoff();
  83. }
  84. public void StartFaceoff()
  85. {
  86. if (faceoffInProgress)
  87. {
  88. Debug.LogWarning("Faceoff already in progress!");
  89. return;
  90. }
  91. if (currentFaceoffSpot == null)
  92. {
  93. currentFaceoffSpot = centerIceFaceoffSpot;
  94. }
  95. if (homeCenterPlayer == null || awayCenterPlayer == null)
  96. {
  97. Debug.LogError("Missing center players for faceoff!");
  98. return;
  99. }
  100. StartCoroutine(FaceoffSequence());
  101. }
  102. private IEnumerator FaceoffSequence()
  103. {
  104. faceoffInProgress = true;
  105. Debug.Log("Faceoff starting...");
  106. // Stop timer and freeze players during faceoff
  107. if (GameManager.Instance != null)
  108. {
  109. GameManager.Instance.SetPuckInPlay(false);
  110. }
  111. // Position puck at faceoff spot
  112. if (puck != null && currentFaceoffSpot != null)
  113. {
  114. puck.transform.position = currentFaceoffSpot.position + Vector3.up * 0.025f; // Slight lift
  115. puck.Release(); // Make sure puck is free
  116. }
  117. FreezeAllPlayers(true);
  118. yield return new WaitForSeconds(1.0f);
  119. currentOutcome = DetermineFaceoffOutcome();
  120. Debug.Log($"Faceoff outcome: {currentOutcome}");
  121. yield return new WaitForSeconds(faceoffDuration);
  122. switch (currentOutcome)
  123. {
  124. case FaceoffOutcome.HomeWin:
  125. ExecuteCleanWin(homeCenterPlayer, homeTeam, awayTeam);
  126. break;
  127. case FaceoffOutcome.AwayWin:
  128. ExecuteCleanWin(awayCenterPlayer, awayTeam, homeTeam);
  129. break;
  130. case FaceoffOutcome.Struggle:
  131. ExecuteStruggle();
  132. yield return new WaitForSeconds(struggleDuration);
  133. break;
  134. }
  135. FreezeAllPlayers(false);
  136. faceoffInProgress = false;
  137. // Start timer and allow movement after faceoff completes
  138. if (GameManager.Instance != null)
  139. {
  140. GameManager.Instance.SetPuckInPlay(true);
  141. }
  142. Debug.Log("Faceoff complete!");
  143. }
  144. private FaceoffOutcome DetermineFaceoffOutcome()
  145. {
  146. if (homeCenterPlayer == null || awayCenterPlayer == null)
  147. return FaceoffOutcome.Struggle;
  148. float homeStrength = CalculateFaceoffStrength(homeCenterPlayer);
  149. float awayStrength = CalculateFaceoffStrength(awayCenterPlayer);
  150. float totalStrength = homeStrength + awayStrength;
  151. float homeWinChance = homeStrength / totalStrength;
  152. if (Mathf.Abs(homeWinChance - 0.5f) < (0.5f - winThreshold))
  153. {
  154. return FaceoffOutcome.Struggle;
  155. }
  156. float roll = Random.value;
  157. if (roll < struggleChance)
  158. {
  159. return FaceoffOutcome.Struggle;
  160. }
  161. return Random.value < homeWinChance ? FaceoffOutcome.HomeWin : FaceoffOutcome.AwayWin;
  162. }
  163. private float CalculateFaceoffStrength(PlayerController player)
  164. {
  165. return (player.stats.strength * 0.3f) +
  166. (player.stats.stickHandling * 0.3f) +
  167. (player.stats.awareness * 0.2f) +
  168. (player.stats.agility * 0.2f);
  169. }
  170. private void ExecuteCleanWin(PlayerController winner, List<PlayerController> winningTeam, List<PlayerController> losingTeam)
  171. {
  172. Debug.Log($"{winner.stats.playerName} wins the faceoff cleanly!");
  173. // Find best pass target FIRST (before assigning puck)
  174. PlayerController target = ChoosePassTarget(winner, winningTeam);
  175. if (target != null && target != winner)
  176. {
  177. Debug.Log($"Puck sent to {target.stats.playerName} at position {target.transform.position}");
  178. // Calculate direction from FACEOFF SPOT (not puck position, since puck might be carried)
  179. Vector3 startPosition = currentFaceoffSpot.position;
  180. Vector3 targetPosition = target.transform.position;
  181. // Keep pass flat on the ice (ignore Y difference)
  182. startPosition.y = 0.025f;
  183. targetPosition.y = 0.025f;
  184. Vector3 passDirection = (targetPosition - startPosition).normalized;
  185. float distance = Vector3.Distance(startPosition, targetPosition);
  186. float adjustedForce = passForce * Mathf.Clamp(distance / 5f, 0.8f, 2f);
  187. Debug.Log($"Pass direction: {passDirection}, Distance: {distance:F2}m, Force: {adjustedForce:F2}");
  188. // Make sure puck is free and at faceoff spot
  189. if (puck != null)
  190. {
  191. puck.Release();
  192. // Wait one frame to ensure Release() completes
  193. StartCoroutine(ExecutePassAfterFrame(startPosition, passDirection, adjustedForce, target));
  194. }
  195. }
  196. else
  197. {
  198. Debug.Log($"{winner.stats.playerName} keeps the puck!");
  199. if (puck != null)
  200. {
  201. puck.AssignToPlayer(winner);
  202. }
  203. }
  204. }
  205. private IEnumerator ExecutePassAfterFrame(Vector3 startPosition, Vector3 direction, float force, PlayerController target)
  206. {
  207. yield return new WaitForFixedUpdate(); // Wait for physics to settle
  208. if (puck != null)
  209. {
  210. // Position puck at faceoff spot
  211. puck.transform.position = startPosition;
  212. // Get rigidbody and zero everything
  213. Rigidbody puckRb = puck.GetComponent<Rigidbody>();
  214. if (puckRb != null)
  215. {
  216. puckRb.linearVelocity = Vector3.zero;
  217. puckRb.angularVelocity = Vector3.zero;
  218. // Apply force in next physics frame
  219. yield return new WaitForFixedUpdate();
  220. puckRb.AddForce(direction * force, ForceMode.VelocityChange);
  221. Debug.Log($"Applied force: {direction * force}");
  222. }
  223. }
  224. // Delay assignment to let puck travel
  225. StartCoroutine(AssignPuckAfterDelay(target, 0.5f));
  226. }
  227. private IEnumerator AssignPuckAfterDelay(PlayerController target, float delay)
  228. {
  229. yield return new WaitForSeconds(delay);
  230. // Check if puck is close enough to target
  231. if (puck != null && target != null && puck.IsLoose)
  232. {
  233. float distance = Vector3.Distance(puck.transform.position, target.transform.position);
  234. if (distance < 4f) // Increased pickup range further
  235. {
  236. // Stop the puck's movement before assigning
  237. Rigidbody puckRb = puck.GetComponent<Rigidbody>();
  238. if (puckRb != null)
  239. {
  240. puckRb.linearVelocity = Vector3.zero;
  241. puckRb.angularVelocity = Vector3.zero;
  242. }
  243. puck.AssignToPlayer(target);
  244. Debug.Log($"{target.stats.playerName} received the puck!");
  245. }
  246. else
  247. {
  248. Debug.Log($"Puck too far from {target.stats.playerName} ({distance:F1}m) - Puck at {puck.transform.position}, Target at {target.transform.position}");
  249. }
  250. }
  251. }
  252. private void ExecuteStruggle()
  253. {
  254. Debug.Log("Faceoff is a struggle! Puck is loose!");
  255. if (puck != null)
  256. {
  257. puck.Release();
  258. // Launch puck in random direction
  259. Vector3 randomDirection = new Vector3(
  260. Random.Range(-1f, 1f),
  261. 0.1f, // Slight upward component
  262. Random.Range(-1f, 1f)
  263. ).normalized;
  264. puck.ApplyForce(randomDirection * struggleLaunchForce, ForceMode.Impulse);
  265. }
  266. StartCoroutine(ResolveStruggle());
  267. }
  268. private IEnumerator ResolveStruggle()
  269. {
  270. yield return new WaitForSeconds(struggleDuration * 0.5f);
  271. // Find closest player to puck
  272. PlayerController closestPlayer = null;
  273. float closestDistance = float.MaxValue;
  274. List<PlayerController> allPlayers = new List<PlayerController>();
  275. allPlayers.AddRange(homeTeam);
  276. allPlayers.AddRange(awayTeam);
  277. foreach (var player in allPlayers)
  278. {
  279. float distance = Vector3.Distance(player.transform.position, puck.transform.position);
  280. if (distance < closestDistance)
  281. {
  282. closestDistance = distance;
  283. closestPlayer = player;
  284. }
  285. }
  286. if (closestPlayer != null && closestDistance < 3f)
  287. {
  288. puck.AssignToPlayer(closestPlayer);
  289. Debug.Log($"{closestPlayer.stats.playerName} gains control after struggle!");
  290. }
  291. }
  292. private PlayerController ChoosePassTarget(PlayerController winner, List<PlayerController> team)
  293. {
  294. // Prefer defenders for breakout
  295. List<PlayerController> defenders = team.Where(p =>
  296. p != winner && (p.stats.position == PlayerPosition.LD || p.stats.position == PlayerPosition.RD)
  297. ).ToList();
  298. if (defenders.Count > 0)
  299. {
  300. // Choose closest defender
  301. return defenders.OrderBy(d =>
  302. Vector3.Distance(d.transform.position, winner.transform.position)
  303. ).First();
  304. }
  305. // If no defenders, choose any teammate
  306. List<PlayerController> availableTeammates = team.Where(p => p != winner).ToList();
  307. if (availableTeammates.Count > 0)
  308. {
  309. return availableTeammates[Random.Range(0, availableTeammates.Count)];
  310. }
  311. return winner;
  312. }
  313. private void FreezeAllPlayers(bool freeze)
  314. {
  315. foreach (var player in homeTeam)
  316. {
  317. if (player != null)
  318. {
  319. player.SetFrozen(freeze);
  320. }
  321. }
  322. foreach (var player in awayTeam)
  323. {
  324. if (player != null)
  325. {
  326. player.SetFrozen(freeze);
  327. }
  328. }
  329. }
  330. public void InitiateFaceoff(Vector3 faceoffPosition)
  331. {
  332. if (currentFaceoffSpot == null)
  333. {
  334. GameObject spot = new GameObject("FaceoffSpot");
  335. currentFaceoffSpot = spot.transform;
  336. }
  337. currentFaceoffSpot.position = faceoffPosition;
  338. StartFaceoff();
  339. }
  340. void Update()
  341. {
  342. if (Keyboard.current != null && !faceoffInProgress)
  343. {
  344. if (Keyboard.current.fKey.wasPressedThisFrame)
  345. {
  346. StartFaceoffAt(centerIceFaceoffSpot);
  347. }
  348. else if (Keyboard.current.digit1Key.wasPressedThisFrame)
  349. {
  350. StartFaceoffAt(homeZoneLeftFaceoffSpot);
  351. }
  352. else if (Keyboard.current.digit2Key.wasPressedThisFrame)
  353. {
  354. StartFaceoffAt(homeZoneRightFaceoffSpot);
  355. }
  356. else if (Keyboard.current.digit3Key.wasPressedThisFrame)
  357. {
  358. StartFaceoffAt(awayZoneLeftFaceoffSpot);
  359. }
  360. else if (Keyboard.current.digit4Key.wasPressedThisFrame)
  361. {
  362. StartFaceoffAt(awayZoneRightFaceoffSpot);
  363. }
  364. else if (Keyboard.current.digit5Key.wasPressedThisFrame)
  365. {
  366. StartFaceoffAt(homeZoneBlueLineLeftFaceoffSpot);
  367. }
  368. else if (Keyboard.current.digit6Key.wasPressedThisFrame)
  369. {
  370. StartFaceoffAt(homeZoneBlueLineRightFaceoffSpot);
  371. }
  372. else if (Keyboard.current.digit7Key.wasPressedThisFrame)
  373. {
  374. StartFaceoffAt(awayZoneBlueLineLeftFaceoffSpot);
  375. }
  376. else if (Keyboard.current.digit8Key.wasPressedThisFrame)
  377. {
  378. StartFaceoffAt(awayZoneBlueLineRightFaceoffSpot);
  379. }
  380. }
  381. }
  382. }