using System.Collections.Generic;
using Fusion;
using UnityEngine;
namespace Projectiles
{
///
/// Represents the actual gameplay loop. Handles PlayerAgent spawning and despawning for each Player that joins gameplay.
///
public class Gameplay : ContextBehaviour
{
// PUBLIC MEMBERS
[Networked, Capacity(200)]
public NetworkDictionary Players { get; }
// PRIVATE METHODS
private SpawnPoint[] _spawnPoints;
private int _lastSpawnPoint = -1;
private List _spawnRequests = new();
// PUBLIC METHODS
public void Join(Player player)
{
if (HasStateAuthority == false)
return;
var playerRef = player.Object.InputAuthority;
if (Players.ContainsKey(playerRef) == true)
{
Debug.LogError($"Player {playerRef} already joined");
return;
}
Players.Add(playerRef, player);
OnPlayerJoined(player);
}
public void Leave(Player player)
{
if (HasStateAuthority == false)
return;
if (Players.ContainsKey(player.Object.InputAuthority) == false)
return;
Players.Remove(player.Object.InputAuthority);
OnPlayerLeft(player);
}
// NetworkBehaviour INTERFACE
public override void Spawned()
{
// Register to context
Context.Gameplay = this;
}
public override void FixedUpdateNetwork()
{
if (HasStateAuthority == false)
return;
int currentTick = Runner.Tick;
for (int i = _spawnRequests.Count - 1; i >= 0; i--)
{
var request = _spawnRequests[i];
if (request.Tick > currentTick)
continue;
_spawnRequests.RemoveAt(i);
if (request.Player == null || request.Player.Object == null)
continue; // Player no longer valid
if (Players.ContainsKey(request.Player.Object.InputAuthority) == false)
continue; // Player left gameplay
SpawnPlayerAgent(request.Player);
}
}
public override void Despawned(NetworkRunner runner, bool hasState)
{
// Clear from context
Context.Gameplay = null;
}
// PROTECTED METHODS
protected virtual void OnPlayerJoined(Player player)
{
SpawnPlayerAgent(player);
}
protected virtual void OnPlayerLeft(Player player)
{
DespawnPlayerAgent(player);
}
protected virtual void OnPlayerDeath(Player player)
{
AddSpawnRequest(player, 3f);
}
protected virtual void OnPlayerAgentSpawned(PlayerAgent agent)
{
agent.Health.SetImmortality(3f);
}
protected virtual void OnPlayerAgentDespawned(PlayerAgent agent)
{
}
protected void SpawnPlayerAgent(Player player)
{
DespawnPlayerAgent(player);
var agent = SpawnAgent(player.Object.InputAuthority, player.AgentPrefab) as PlayerAgent;
player.AssignAgent(agent);
agent.Health.FatalHitTaken += OnFatalHitTaken;
OnPlayerAgentSpawned(agent);
}
protected void DespawnPlayerAgent(Player player)
{
if (player.ActiveAgent == null)
return;
player.ActiveAgent.Health.FatalHitTaken -= OnFatalHitTaken;
OnPlayerAgentDespawned(player.ActiveAgent);
DespawnAgent(player.ActiveAgent);
player.ClearAgent();
}
protected void AddSpawnRequest(Player player, float spawnDelay)
{
int delayTicks = Mathf.RoundToInt(Runner.TickRate * spawnDelay);
_spawnRequests.Add(new SpawnRequest()
{
Player = player,
Tick = Runner.Tick + delayTicks,
});
}
// PRIVATE METHODS
private void OnFatalHitTaken(HitData hitData)
{
var health = hitData.Target as Health;
if (health == null)
return;
if (Players.TryGet(health.Object.InputAuthority, out Player player) == true)
{
OnPlayerDeath(player);
}
}
private PlayerAgent SpawnAgent(PlayerRef inputAuthority, PlayerAgent agentPrefab)
{
if (_spawnPoints == null)
{
_spawnPoints = Runner.SimulationUnityScene.FindObjectsOfTypeInOrder(false);
}
_lastSpawnPoint = (_lastSpawnPoint + 1) % _spawnPoints.Length;
var spawnPoint = _spawnPoints[_lastSpawnPoint].transform;
var agent = Runner.Spawn(agentPrefab, spawnPoint.position, spawnPoint.rotation, inputAuthority);
return agent;
}
private void DespawnAgent(PlayerAgent agent)
{
if (agent == null)
return;
Runner.Despawn(agent.Object);
}
// HELPERS
public struct SpawnRequest
{
public Player Player;
public int Tick;
}
}
}