PlayerController.cs 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242
  1. using UnityEngine;
  2. using TMPro;
  3. using System;
  4. public class PlayerController : MonoBehaviour
  5. {
  6. [Header("References")]
  7. public PlayerStats stats;
  8. public PlayerMentality mentality;
  9. public AIStrategy currentStrategy;
  10. [Header("UI")]
  11. public TextMeshPro positionLabel;
  12. public GameObject circleMesh;
  13. [Header("Puck Possession")]
  14. [SerializeField] private GameObject possessionIndicator; // Visual indicator when player has puck
  15. [SerializeField] private float pickupCooldown = 0.5f;
  16. private bool hasPuck = false;
  17. private PuckController controlledPuck;
  18. private float lastPickupTime = 0f;
  19. private bool isFrozen = true; // Start frozen - requires faceoff to begin
  20. private Rigidbody rb;
  21. void Awake()
  22. {
  23. rb = GetComponent<Rigidbody>();
  24. // Start frozen - make kinematic immediately
  25. if (rb != null)
  26. {
  27. rb.linearVelocity = Vector3.zero;
  28. rb.angularVelocity = Vector3.zero;
  29. rb.isKinematic = true;
  30. }
  31. }
  32. void Start()
  33. {
  34. SetupVisuals();
  35. }
  36. void Update()
  37. {
  38. UpdatePossessionIndicator();
  39. }
  40. void SetupVisuals()
  41. {
  42. // Create circle (use a cylinder with flat height)
  43. if (circleMesh == null)
  44. {
  45. circleMesh = GameObject.CreatePrimitive(PrimitiveType.Cylinder);
  46. circleMesh.transform.SetParent(transform);
  47. circleMesh.transform.localPosition = Vector3.zero;
  48. circleMesh.transform.localScale = new Vector3(1f, 0.1f, 1f);
  49. }
  50. // Create position label
  51. if (positionLabel == null)
  52. {
  53. GameObject labelObj = new GameObject("PositionLabel");
  54. labelObj.transform.SetParent(transform);
  55. labelObj.transform.localPosition = new Vector3(0, 0.5f, 0);
  56. positionLabel = labelObj.AddComponent<TextMeshPro>();
  57. positionLabel.alignment = TextAlignmentOptions.Center;
  58. positionLabel.fontSize = 4;
  59. }
  60. positionLabel.text = stats.position.ToString();
  61. // Create possession indicator (ring around player)
  62. if (possessionIndicator == null)
  63. {
  64. possessionIndicator = GameObject.CreatePrimitive(PrimitiveType.Sphere);
  65. if (possessionIndicator.GetComponent<MeshFilter>() == null)
  66. {
  67. // Fallback: create a thin cylinder ring
  68. possessionIndicator = GameObject.CreatePrimitive(PrimitiveType.Cylinder);
  69. }
  70. possessionIndicator.transform.SetParent(transform);
  71. possessionIndicator.transform.localPosition = new Vector3(0, 0.2f, 0);
  72. possessionIndicator.transform.localScale = new Vector3(1.5f, 0.05f, 1.5f);
  73. // Make it bright and visible
  74. Renderer renderer = possessionIndicator.GetComponent<Renderer>();
  75. if (renderer != null)
  76. {
  77. renderer.material.color = Color.yellow;
  78. renderer.material.EnableKeyword("_EMISSION");
  79. renderer.material.SetColor("_EmissionColor", Color.yellow);
  80. }
  81. // Remove collider from indicator
  82. Collider indicatorCollider = possessionIndicator.GetComponent<Collider>();
  83. if (indicatorCollider != null)
  84. {
  85. Destroy(indicatorCollider);
  86. }
  87. possessionIndicator.SetActive(false);
  88. }
  89. }
  90. void UpdatePossessionIndicator()
  91. {
  92. if (possessionIndicator != null)
  93. {
  94. possessionIndicator.SetActive(hasPuck);
  95. // Rotate for visual effect
  96. if (hasPuck)
  97. {
  98. possessionIndicator.transform.Rotate(Vector3.up, 180f * Time.deltaTime);
  99. }
  100. }
  101. }
  102. public void SetFrozen(bool frozen)
  103. {
  104. isFrozen = frozen;
  105. if (rb != null)
  106. {
  107. if (frozen)
  108. {
  109. // Immediately stop all motion
  110. rb.linearVelocity = Vector3.zero;
  111. rb.angularVelocity = Vector3.zero;
  112. // Make kinematic to completely prevent physics interactions
  113. rb.isKinematic = true;
  114. }
  115. else
  116. {
  117. // Restore physics
  118. rb.isKinematic = false;
  119. }
  120. }
  121. }
  122. public bool IsFrozen()
  123. {
  124. return isFrozen;
  125. }
  126. public void GainPuck(PuckController newPuck)
  127. {
  128. controlledPuck = newPuck;
  129. hasPuck = true;
  130. lastPickupTime = Time.time;
  131. Debug.Log($"{stats.playerName} gained control of the puck!");
  132. }
  133. public void ReleasePuck()
  134. {
  135. if (controlledPuck != null)
  136. {
  137. controlledPuck = null;
  138. hasPuck = false;
  139. }
  140. }
  141. public bool CanPickupPuck()
  142. {
  143. // Can only pick up if not recently picked up and not frozen
  144. return !hasPuck && !isFrozen && (Time.time - lastPickupTime) > pickupCooldown;
  145. }
  146. public bool HasPuck()
  147. {
  148. return hasPuck;
  149. }
  150. public void Shoot(Vector3 targetDirection, float powerMultiplier = 1.0f)
  151. {
  152. if (!hasPuck || controlledPuck == null) return;
  153. // Calculate shot accuracy based on stats
  154. float accuracy = stats.shotAccuracy / 100f;
  155. float power = stats.shotPower / 100f;
  156. // Add some inaccuracy based on stats
  157. Vector3 direction = targetDirection;
  158. float inaccuracy = (1f - accuracy) * 15f; // Up to 15 degrees off
  159. direction = Quaternion.Euler(0, UnityEngine.Random.Range(-inaccuracy, inaccuracy), 0) * direction;
  160. // Apply force to puck
  161. float shotForce = power * 30f * powerMultiplier; // Base force multiplied by power stat
  162. controlledPuck.ApplyForce(direction.normalized * shotForce, ForceMode.Impulse);
  163. Debug.Log($"{stats.playerName} shoots! Accuracy: {accuracy}, Power: {power}");
  164. }
  165. public void Pass(PlayerController target)
  166. {
  167. if (!hasPuck || controlledPuck == null || target == null) return;
  168. float accuracy = stats.passAccuracy / 100f;
  169. // Calculate pass direction and force based on distance
  170. Vector3 direction = (target.transform.position - transform.position).normalized;
  171. float distance = Vector3.Distance(transform.position, target.transform.position);
  172. // Add inaccuracy
  173. float inaccuracy = (1f - accuracy) * 10f;
  174. direction = Quaternion.Euler(0, UnityEngine.Random.Range(-inaccuracy, inaccuracy), 0) * direction;
  175. // Force scales with distance
  176. float passForce = Mathf.Lerp(10f, 25f, distance / 30f);
  177. controlledPuck.ApplyForce(direction * passForce, ForceMode.Impulse);
  178. Debug.Log($"{stats.playerName} passes to {target.stats.playerName}! Accuracy: {accuracy}");
  179. }
  180. public void Check(PlayerController target)
  181. {
  182. if (target == null) return;
  183. float checkPower = stats.checking / 100f;
  184. bool checkSuccess = UnityEngine.Random.value < checkPower;
  185. if (checkSuccess && target.HasPuck())
  186. {
  187. // Successful check - target loses puck
  188. Debug.Log($"{stats.playerName} checks {target.stats.playerName} successfully!");
  189. if (target.controlledPuck != null)
  190. {
  191. // Knock puck loose
  192. Vector3 knockDirection = (target.transform.position - transform.position).normalized;
  193. target.controlledPuck.ApplyForce(knockDirection * 8f, ForceMode.Impulse);
  194. }
  195. }
  196. else
  197. {
  198. Debug.Log($"{stats.playerName} checks {target.stats.playerName} but fails to dislodge puck");
  199. }
  200. }
  201. }