using System.Collections;
using Unity.Netcode;
using UnityEngine;
using UnityEngine.XR.Content.Interaction;
namespace XRMultiplayer.MiniGames
{
///
/// Represents a networked version of the Whack-A-Pig mini game.
///
public class NetworkedWhackAPig : NetworkBehaviour
{
///
/// The proxy pigs to use for showing the trick.
///
[SerializeField] GameObject[] m_ProxyPigs;
///
/// The hammers to use for hitting the pigs.
///
[SerializeField] NetworkPhysicsInteractable[] m_Hammers;
[SerializeField] Collider[] m_HammerIgnoreColliders;
///
/// The pig prefab to spawn.
///
[SerializeField] GameObject m_PigPrefab;
///
/// The bad pig prefab to spawn.
///
[SerializeField] GameObject m_BadPigPrefab;
///
/// The time to stay spawned before despawning.
///
[SerializeField] Vector2 m_TimeToStaySpawnedMinMax = new Vector2(.5f, 1.0f);
///
/// The time to wait before spawning a new pig.
///
[SerializeField] float m_TimeToSpawn = .5f;
///
/// The time to show the proxy pig.
///
[SerializeField] float m_ProxyShowTime = .35f;
///
/// The hidden height of the proxy pig.
///
[SerializeField] float m_HiddenHeight = -0.25f;
[SerializeField] float m_SpawnStartHeight = 0.0f;
[SerializeField] float m_SpawnShowHeight = 0.1f;
///
/// The trick height min and max for the proxy pig.
///
[SerializeField] Vector2 m_TrickHeightMinMax = new Vector2(-0.25f, -.05f);
///
/// The trick time min and max for the proxy pig.
///
[SerializeField] Vector2 m_TrickTimeMinMax = new Vector2(1.0f, 3.0f);
///
/// The bad pig spawn chance.
///
[SerializeField] float m_BadPigSpawn = .7f;
[SerializeField] Collider gameBarrierCollider;
///
/// The mini game to use for handling the mini game logic.
///
MiniGame_Whack m_MiniGame;
///
/// The current breakable pig.
///
Breakable m_CurrentBreakablePig;
///
/// The current routine being played.
///
IEnumerator m_CurrentRoutine;
///
/// The current proxy ID.
///
int m_CurrentProxyId = 0;
///
/// Whether we are currently spawning a pig.
///
bool m_Spawning = false;
///
void Start()
{
TryGetComponent(out m_MiniGame);
foreach (var pig in m_ProxyPigs)
{
pig.SetActive(false);
}
foreach (var areaCollider in m_HammerIgnoreColliders)
{
foreach (var hammer in m_Hammers)
{
foreach (var hammerInteractableCollider in hammer.baseInteractable.colliders)
{
Physics.IgnoreCollision(hammerInteractableCollider, areaCollider);
}
}
}
}
///
/// Starts the trick routine.
///
[ContextMenu("Test Trick Routine")]
void ShowTrickRoutine()
{
if (m_CurrentRoutine != null) StopTrickRoutine();
m_CurrentRoutine = TrickRoutine();
StartCoroutine(m_CurrentRoutine);
}
///
/// Stops the trick routine.
///
[ContextMenu("Stop Trick Routine")]
void StopTrickRoutine()
{
if (m_CurrentRoutine != null) StopCoroutine(m_CurrentRoutine);
m_ProxyPigs[m_CurrentProxyId].SetActive(false);
m_ProxyPigs[m_CurrentProxyId].transform.localPosition = new Vector3(m_ProxyPigs[m_CurrentProxyId].transform.localPosition.x, m_HiddenHeight, m_ProxyPigs[m_CurrentProxyId].transform.localPosition.z);
}
///
/// Performs the trick routine.
///
/// An IEnumerator representing the trick routine.
IEnumerator TrickRoutine()
{
while (true & !m_MiniGame.finished)
{
// Choose a random proxy pig to show
m_CurrentProxyId = Random.Range(0, m_ProxyPigs.Length);
float riseTime = m_ProxyShowTime / 2;
Vector3 startPosition = new Vector3(m_ProxyPigs[m_CurrentProxyId].transform.localPosition.x, 0, m_ProxyPigs[m_CurrentProxyId].transform.localPosition.z);
m_ProxyPigs[m_CurrentProxyId].transform.localPosition = startPosition + Vector3.up * m_HiddenHeight;
m_ProxyPigs[m_CurrentProxyId].SetActive(true);
float trickHeight = Random.Range(m_TrickHeightMinMax.x, m_TrickHeightMinMax.y);
// Move the pig up to the trick height
for (float i = 0; i < riseTime; i += Time.deltaTime)
{
float perc = i / riseTime;
float lerpHeight = Mathf.Lerp(m_HiddenHeight, trickHeight, perc);
m_ProxyPigs[m_CurrentProxyId].transform.localPosition = startPosition + Vector3.up * lerpHeight;
yield return null;
}
m_ProxyPigs[m_CurrentProxyId].transform.localPosition = startPosition + Vector3.up * trickHeight;
// Move the pig back down to the hidden height
for (float i = 0; i < riseTime; i += Time.deltaTime)
{
float perc = i / riseTime;
float lerpHeight = Mathf.Lerp(trickHeight, m_HiddenHeight, perc);
m_ProxyPigs[m_CurrentProxyId].transform.localPosition = startPosition + Vector3.up * lerpHeight;
yield return null;
}
m_ProxyPigs[m_CurrentProxyId].transform.localPosition = startPosition + Vector3.up * m_HiddenHeight;
// Hide the pig
m_ProxyPigs[m_CurrentProxyId].SetActive(false);
}
}
///
/// Spawns a pig on the server.
///
public void SpawnProcessServer()
{
if (IsServer)
SpawnProcessClientRpc(Random.Range(m_TrickTimeMinMax.x, m_TrickTimeMinMax.y));
}
///
/// Spawns a pig on the clients.
///
/// The time to wait before spawning the pig.
[ClientRpc]
public void SpawnProcessClientRpc(float waitTime)
{
m_Spawning = true;
StartCoroutine(SpawnAfterTime(waitTime));
}
///
/// Spawns a pig after a certain amount of time.
///
/// The time to wait before spawning the pig.
/// An IEnumerator representing the spawn after time routine.
IEnumerator SpawnAfterTime(float time)
{
yield return new WaitForSeconds(.25f);
ShowTrickRoutine();
yield return new WaitForSeconds(time);
SpawnNewPig();
}
///
/// Called from mini game manager when entering pre game state.
///
public void ResetGame()
{
StopTrickRoutine();
StopAllCoroutines();
if (m_CurrentBreakablePig != null)
Destroy(m_CurrentBreakablePig.gameObject);
}
///
/// Ends the game and cleans up.
///
public void EndGame()
{
StopTrickRoutine();
StopAllCoroutines();
if (m_CurrentBreakablePig != null)
Destroy(m_CurrentBreakablePig.gameObject);
}
///
/// Spawns a new pig on the server.
///
public void SpawnNewPig()
{
if (!IsServer) return;
SpawnPigClientRpc(Random.Range(0, m_ProxyPigs.Length), Random.value, Random.Range(m_TimeToStaySpawnedMinMax.x, m_TimeToStaySpawnedMinMax.y));
}
///
/// Spawns a pig on the clients.
///
/// The index of the spawn transform to use.
/// A random value used to determine if a bad pig should be spawned.
[ClientRpc]
void SpawnPigClientRpc(int spawnIdx, float randomValue, float timeToStaySpawned)
{
StopTrickRoutine();
m_Spawning = false;
m_CurrentRoutine = SpawnRoutine(spawnIdx, randomValue, timeToStaySpawned);
StartCoroutine(m_CurrentRoutine);
}
///
/// Spawns a pig on the server and sets up collision ignoring.
///
/// The index of the spawn transform to use.
/// A random value used to determine if a bad pig should be spawned.
/// An IEnumerator representing the spawn routine.
IEnumerator SpawnRoutine(int spawnIdx, float randomValue, float timeToStaySpawned)
{
Vector3 spawnPos = m_ProxyPigs[spawnIdx].transform.position + (Vector3.up * m_SpawnStartHeight);
// Determine if we are spawning a bad pig or a good pig
m_CurrentBreakablePig = Instantiate(randomValue > m_BadPigSpawn ? m_BadPigPrefab : m_PigPrefab, spawnPos, m_ProxyPigs[spawnIdx].transform.rotation).GetComponent();
m_CurrentBreakablePig.collider.enabled = false;
// Updates Physics Ignore Collision to non local hammers cannot interact with the pigs
foreach (var hammer in m_Hammers)
{
if (!hammer.IsOwner || !hammer.isInteracting)
{
foreach (var collider in hammer.baseInteractable.colliders)
{
Physics.IgnoreCollision(collider, m_CurrentBreakablePig.collider);
}
}
}
m_CurrentBreakablePig.onBreak += LocalPigDestroyed;
Vector3 startPosition = m_CurrentBreakablePig.transform.position;
// Move the pig up to the show height
for (float i = 0; i < m_TimeToSpawn; i += Time.deltaTime)
{
float perc = i / m_TimeToSpawn;
float lerpHeight = Mathf.Lerp(m_SpawnStartHeight, m_SpawnShowHeight, perc);
m_CurrentBreakablePig.transform.position = startPosition + Vector3.up * lerpHeight;
yield return null;
}
m_CurrentBreakablePig.transform.position = startPosition + Vector3.up * m_SpawnShowHeight;
m_CurrentBreakablePig.collider.enabled = true;
yield return new WaitForSeconds(timeToStaySpawned);
m_CurrentBreakablePig.collider.enabled = false;
// Move the pig back down to the hidden height
for (float i = 0; i < m_TimeToSpawn; i += Time.deltaTime)
{
float perc = i / m_TimeToSpawn;
float lerpHeight = Mathf.Lerp(m_SpawnShowHeight, m_SpawnStartHeight, perc);
m_CurrentBreakablePig.transform.position = startPosition + Vector3.up * lerpHeight;
yield return null;
}
m_CurrentBreakablePig.transform.position = startPosition + Vector3.up * m_SpawnStartHeight;
// Destroy the pig and spawn a new one if we are the server
Destroy(m_CurrentBreakablePig.gameObject);
if (IsServer)
SpawnProcessServer();
}
///
/// Handles the destruction of a pig on the local client.
///
/// The collider of the pig that was destroyed.
void LocalPigDestroyed(Collider collider)
{
NetworkPhysicsInteractable grabInteractable = collider.GetComponentInParent();
if (grabInteractable == null) // Probably in editor testing
{
if (IsServer & !m_Spawning)
SpawnProcessServer();
}
else if (grabInteractable.IsOwner)
{
m_MiniGame.LocalPlayerScored(m_CurrentBreakablePig.pointValue);
DestroyPigServerRpc(grabInteractable.OwnerClientId);
StopCoroutine(m_CurrentRoutine);
}
}
///
/// Destroys a pig on the server and notifies the clients.
///
/// The client ID of the owner of the pig.
[ServerRpc(RequireOwnership = false)]
void DestroyPigServerRpc(ulong clientId)
{
DestroyPigClientRpc(clientId);
}
///
/// Destroys a pig on the clients.
///
/// The client ID of the owner of the pig.
[ClientRpc]
void DestroyPigClientRpc(ulong clientId)
{
if (XRINetworkPlayer.LocalPlayer.OwnerClientId != clientId && m_CurrentBreakablePig != null)
{
StopCoroutine(m_CurrentRoutine);
m_CurrentBreakablePig.onBreak -= LocalPigDestroyed;
m_CurrentBreakablePig.Break(null);
}
if (IsServer & !m_Spawning)
SpawnProcessServer();
}
}
}