FaceoffManager.cs 14 KB

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