using System.Collections; using Drawing; using Reset.Core; using Sirenix.OdinInspector; using UnityEngine; using Unity.Netcode; namespace Reset.Units{ public class Player : Unit, IKillable, IInteractable{ // IKillable [ShowInInspector] public float maxHealth{ get; set; } public float currentHealth{ get; set; } // State management (don't like this being used like this) public NetworkVariable _isDowned; public bool IsDowned{ get{ if (UnitIsLocal()){ _isDowned.Value = FSM.currentRootStateName == "Downed"; } return _isDowned.Value; } } // References public GameObject pickupTarget; // NOTE: Might be removed in a refactor (https://thunderstar.codecks.io/card/15v-refactor-interaction-handler) // (TEMP) Revive UI // NOTE: When I make the actual UI it's a good idea to have them somehow inherit from a class or something that // will make them persist for more than a frame since Rpc calls will make it flash public float persistDrawingRevive; public float lastKnownReviveTime; void Awake(){ } void AttachToGame(){ if (IsLocalPlayer || !UnitIsNetworked()) { // PlayerManager.Player = gameObject; PlayerManager.RequestNewController(); GetComponent().AttachCamera(gameObject); } } public override void UnitStart(){ base.UnitStart(); SetPlayerName(); AttachToGame(); ((IKillable)this).IKillableInitialize(); } private void SetPlayerName(){ name = "Player"; if (UnitIsNetworked()){ name += IsLocalPlayer ? ", Local" : ", Network"; } } protected override void Update(){ base.Update(); // Draw Revive UI for at least .5 seconds to prevent flashing if (persistDrawingRevive > 0) { persistDrawingRevive -= 1f * Time.deltaTime; DrawReviveBarRpc(lastKnownReviveTime); } } public void TakeDamage(DamageSource[] sources){ foreach (DamageSource source in sources) { TakeDamage(source); } } public void TakeDamage(DamageSource source){ // Calculate health after damage, locally float newHealth = ((IKillable)this).currentHealth - source.damageDealt; // Tell every unit to set the new health value if (UnitIsNetworked()) { SetHealthRpc(newHealth); } else { SetHealth(newHealth); } } [Rpc(SendTo.Everyone)] public void SetHealthRpc(float health){ // Set health to new value, clamped to 0 SetHealth(health); // For local players, run things based on health value. // This Rpc is global but only the owner checks health CheckHealth(); } private void SetHealth(float health){ health = Mathf.Max(health, 0f); currentHealth = health; } void CheckHealth(){ if (UnitIsLocal()){ if (currentHealth <= 0f) { Down(); } } } void Down(){ Graph.SendEvent("Downed"); } public void Kill(){ Graph.SendEvent("Killed"); } public void Interact(){ // Interaction for picking up allies // Check if the other player can be interacted with at all if (pickupTarget&& pickupTarget.GetComponent().CanInteract()) { // Tell the local player to start picking up the ally and switch states Graph.SendEvent("Picking Up Ally"); // Tell the target player to start getting picked up. pickupTarget.GetComponent().StartPickupRpc(); // Wait for the pickup timer to finish StartCoroutine(PickupTimer()); } } [Rpc(SendTo.Owner)] public void StartPickupRpc(){ // When picked up by another player, move into the pick up state Graph.SendEvent("Pick Up Start"); } IEnumerator PickupTimer(){ // Start a timer and wait for it to complete float elapsed = 0f; while (elapsed < 7f) { elapsed += 1f * Time.deltaTime; yield return null; } Graph.SendEvent("Pick Up Success"); } public bool CanInteract(){ return IsDowned; } public void CancelInteract(){ Graph.SendEvent("Pick Up Failed"); } public void OnObserverDetected(EnvironmentObserver observer){ // Try and get a Player component from the current hit object // The rest of the logic will continue as expected so long as an pickupTarget = observer.hit.collider.gameObject; DrawInteractStatus(); } void DrawInteractStatus(){ using (Draw.WithColor(Color.blue)) { Draw.ingame.Label2D(transform.position + Vector3.up * 2.5f, "Interactable", Color.orchid); } } [Rpc(SendTo.Everyone)] public void DrawReviveBarRpc(float elapsedTime){ // Draw a (shoddy) UI bar that shows the revie progress using (Draw.ingame.WithLineWidth(5f)) { using (Draw.InLocalSpace(transform)){ // Set width of the bar float width = .9f; float widthDone = width * elapsedTime / 5f; // 5f assumes a 5 second revive time // Clamp the red bar showing how much is progresseed to the max of width of the purple bar background (to prevent it going outside) widthDone = Mathf.Clamp(widthDone, 0, width); // Draw background bar Vector3 pos = Vector3.up * 1.8f + Vector3.left * width + Vector3.forward * +.01f; Draw.ingame.Line( transform.position + Camera.main.transform.rotation * pos, transform.position + Camera.main.transform.rotation * (pos + Vector3.right * width * 2f), Color.rebeccaPurple ); // Draw foreground bar Vector3 donePos = Vector3.up * 1.8f + Vector3.left * widthDone; Draw.ingame.Line( transform.position + Camera.main.transform.rotation * donePos, transform.position + Camera.main.transform.rotation * (donePos + Vector3.right * widthDone * 2f), Color.red ); } } // Set last known time so that online players don't have flashing UIs lastKnownReviveTime = elapsedTime; } void LateUpdate(){ // Clear the pickupTarget every frame // NOTE: Will this work online? pickupTarget = null; } } }