2025-09-24 11:24:38 +05:00

645 lines
15 KiB
C#

using Fusion;
using System;
namespace TPSBR
{
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public struct KillData : INetworkStruct
{
public PlayerRef KillerRef;
public PlayerRef VictimRef;
public EHitType HitType;
public bool Headshot { get { return _flags.IsBitSet(0); } set { _flags.SetBit(0, value); } }
private byte _flags;
}
public struct RespawnRequest
{
public PlayerRef PlayerRef;
public TickTimer Timer;
}
public enum EGameplayType
{
None,
Deathmatch,
Elimination,
BattleRoyale,
}
public abstract class GameplayMode : ContextBehaviour
{
public enum EState
{
None,
Active,
Finished,
}
public string GameplayName;
public int MaxPlayers;
public short ScorePerKill;
public short ScorePerDeath;
public short ScorePerSuicide;
public float RespawnTime;
public float TimeLimit;
public float BackfillTimeLimit;
public Announcement[] Announcements;
// PUBLIC MEMBERS
public EGameplayType Type => _type;
public float Time => (Runner.Tick - _startTick) * Runner.DeltaTime;
public float RemainingTime => _endTimer.IsRunning == true ? _endTimer.RemainingTime(Runner).Value : 0f;
[Networked, HideInInspector]
public EState State { get; private set; }
public List<SpawnPoint> SpawnPoints => _allSpawnPoints;
public ShrinkingArea ShrinkingArea => _shrinkingArea;
public Action<PlayerRef> OnPlayerJoinedGame;
public Action<string> OnPlayerLeftGame;
public Action<KillData> OnAgentDeath;
public Action<PlayerRef> OnPlayerEliminated;
// PROTECTED MEMBERS
[Networked, HideInInspector]
protected int _startTick { get; set; }
[Networked, HideInInspector]
protected TickTimer _endTimer { get; private set; }
[Networked, HideInInspector]
protected ShrinkingArea _shrinkingArea { get; set; }
// PRIVATE MEMBERS
[SerializeField]
private EGameplayType _type;
[SerializeField]
private bool _useShrinkingArea;
[SerializeField]
private float _maxKillInRowDelay = 3.5f;
private Queue<RespawnRequest> _respawnRequests = new Queue<RespawnRequest>(16);
private List<SpawnPoint> _allSpawnPoints = new List<SpawnPoint>();
private List<SpawnPoint> _availableSpawnPoints = new List<SpawnPoint>();
private DefaultPlayerComparer _playerComparer = new DefaultPlayerComparer();
private float _backfillTimerS;
// PUBLIC METHODS
public void Activate()
{
if (Runner.IsServer == false)
return;
if (State != EState.None)
return;
_startTick = Runner.Tick;
if (TimeLimit > 0f)
{
_endTimer = TickTimer.CreateFromSeconds(Runner, TimeLimit);
}
if (_useShrinkingArea == true && HasStateAuthority == true)
{
_shrinkingArea = Runner.SimulationUnityScene.GetComponent<ShrinkingArea>();
if (_shrinkingArea != null && HasStateAuthority == true)
{
_shrinkingArea.Activate();
_shrinkingArea.ShrinkingAnnounced += OnShrinkingAreaAnnounced;
}
}
Runner.SimulationUnityScene.GetComponents(_allSpawnPoints);
for (int i = 0; i < _allSpawnPoints.Count; i++)
{
if (_allSpawnPoints[i].SpawnEnabled == true)
{
_availableSpawnPoints.Add(_allSpawnPoints[i]);
}
}
if (_shrinkingArea != null && HasStateAuthority == true)
{
OnShrinkingAreaAnnounced(_shrinkingArea.Center, _shrinkingArea.Radius);
}
State = EState.Active;
OnActivate();
}
public void AgentDeath(Agent victim, HitData hitData)
{
if (Runner.IsServer == false)
return;
if (State != EState.Active)
return;
var victimRef = victim.Object.InputAuthority;
var victimPlayer = Context.NetworkGame.GetPlayer(victimRef);
var victimStatistics = victimPlayer != null ? victimPlayer.Statistics : default;
var respawnTime = GetRespawnTime(victimStatistics);
if (respawnTime >= 0f)
{
var respawnTimer = TickTimer.CreateFromSeconds(Runner, respawnTime);
victimStatistics.RespawnTimer = respawnTimer;
_respawnRequests.Enqueue(new RespawnRequest()
{
PlayerRef = victimRef,
Timer = respawnTimer,
});
}
else
{
victimStatistics.IsEliminated = true;
}
victimStatistics.IsAlive = false;
victimStatistics.Deaths += 1;
victimStatistics.Score += ScorePerDeath;
victimStatistics.KillsWithoutDeath = 0;
var killerRef = hitData.InstigatorRef;
var killerPlayer = killerRef.IsRealPlayer == true ? Context.NetworkGame.GetPlayer(killerRef) : default;
var killerStatistics = killerPlayer != null ? killerPlayer.Statistics : default;
if (killerRef == victimRef)
{
victimStatistics.Score += ScorePerSuicide;
}
else
{
killerStatistics.Kills += 1;
killerStatistics.Score += ScorePerKill;
killerStatistics.KillsWithoutDeath += 1;
if (killerStatistics.KillsInRowCooldown.Expired(Runner) == false)
{
killerStatistics.KillsInRow += 1;
}
else
{
killerStatistics.KillsInRow = 1;
}
killerStatistics.KillsInRowCooldown = TickTimer.CreateFromSeconds(Runner, _maxKillInRowDelay);
}
AgentDeath(ref victimStatistics, ref killerStatistics);
if (victimPlayer != null)
{
victimPlayer.UpdateStatistics(victimStatistics);
}
if (killerPlayer != null && killerPlayer != victimPlayer)
{
killerPlayer.UpdateStatistics(killerStatistics);
}
RecalculatePositions();
var killData = new KillData()
{
KillerRef = killerStatistics.PlayerRef,
VictimRef = victimRef,
Headshot = hitData.IsCritical,
HitType = hitData.HitType,
};
RPC_AgentDeath(killData);
if (victimStatistics.IsEliminated == true)
{
RPC_PlayerEliminated(victimRef);
}
CheckWinCondition();
}
public Transform GetRandomSpawnPoint(float minDistanceFromAgents)
{
if (_availableSpawnPoints.SafeCount() == 0)
return null;
while (minDistanceFromAgents > 1.0f)
{
float minSqrDistanceFromAgents = minDistanceFromAgents * minDistanceFromAgents;
for (int i = 0, count = Mathf.Min(5 + _availableSpawnPoints.Count, 25); i < count; ++i)
{
Transform spawnPoint = _availableSpawnPoints.GetRandom().transform;
bool isValid = true;
foreach (var player in Context.NetworkGame.ActivePlayers)
{
if (player == null)
continue;
var agent = player.ActiveAgent;
if (agent == null)
continue;
if (Vector3.SqrMagnitude(agent.transform.position - spawnPoint.position) < minSqrDistanceFromAgents)
{
isValid = false;
break;
}
}
if (isValid == true)
return spawnPoint;
}
minDistanceFromAgents *= 0.5f;
}
return _availableSpawnPoints.GetRandom().transform;
}
public void ChangeSpectatorTarget(bool next)
{
var observedPlayerRef = Context.ObservedPlayerRef;
int playerIndex = 0;
int maxPlayerIndex = 1000;
while (playerIndex < maxPlayerIndex)
{
++playerIndex;
if (observedPlayerRef.AsIndex > maxPlayerIndex)
{
observedPlayerRef = PlayerRef.None;
}
else if (observedPlayerRef.AsIndex < PlayerRef.None.AsIndex)
{
observedPlayerRef = PlayerRef.FromIndex(maxPlayerIndex);
}
observedPlayerRef = PlayerRef.FromIndex(observedPlayerRef.AsIndex + (next == true ? 1 : -1));
Player observedPlayer = Context.NetworkGame.GetPlayer(observedPlayerRef);
if (observedPlayer == null)
continue;
if (observedPlayer.Statistics.IsEliminated == true)
continue;
break;
}
var localPlayer = Context.NetworkGame.GetPlayer(Context.LocalPlayerRef);
localPlayer.SetObservedPlayer(observedPlayerRef);
}
public void PlayerJoined(Player player)
{
var statistics = player.Statistics;
PreparePlayerStatistics(ref statistics);
player.UpdateStatistics(statistics);
if (statistics.IsEliminated == false)
{
TrySpawnAgent(player);
}
RecalculatePositions();
Context.Backfill.PlayerJoined(player);
RPC_PlayerJoinedGame(player.Object.InputAuthority);
}
public void PlayerLeft(Player player)
{
if (Runner.IsServer == false)
return;
if (State == EState.Finished)
return;
player.DespawnAgent();
RecalculatePositions();
Context.Backfill.PlayerLeft(player);
string nickname = player.Nickname;
if (nickname.HasValue() == false)
{
nickname = "Unknown";
}
RPC_PlayerLeftGame(player.Object.InputAuthority, nickname);
CheckWinCondition();
}
public void StopGame()
{
if (HasStateAuthority == false)
{
Global.Networking.StopGame();
return;
}
StartCoroutine(StopGameCoroutine());
}
// NetworkBehaviour INTERFACE
public override void Spawned()
{
Context.GameplayMode = this;
}
public override void Despawned(NetworkRunner runner, bool hasState)
{
if (_shrinkingArea != null)
{
_shrinkingArea.ShrinkingAnnounced -= OnShrinkingAreaAnnounced;
}
}
public override void FixedUpdateNetwork()
{
if (HasStateAuthority == false)
return;
switch (State)
{
case EState.Active: FixedUpdateNetwork_Active(); break;
case EState.Finished: FixedUpdateNetwork_Finished(); break;
}
}
// GameplayMode INTERFACE
protected virtual void OnActivate() { }
protected virtual void TrySpawnAgent(Player player)
{
Transform spawnPoint = GetRandomSpawnPoint(100.0f);
var spawnPosition = spawnPoint != null ? spawnPoint.position : Vector3.zero;
var spawnRotation = spawnPoint != null ? spawnPoint.rotation : Quaternion.identity;
SpawnAgent(player.Object.InputAuthority, spawnPosition, spawnRotation);
}
protected virtual void AgentDeath(ref PlayerStatistics victimStatistics, ref PlayerStatistics killerStatistics)
{
}
protected virtual void PreparePlayerStatistics(ref PlayerStatistics playerStatistics)
{
}
protected virtual void SortPlayers(List<PlayerStatistics> allStatistics)
{
allStatistics.Sort(_playerComparer);
}
protected virtual float GetRespawnTime(PlayerStatistics playerStatistics)
{
return RespawnTime;
}
protected abstract void CheckWinCondition();
// PROTECTED METHODS
protected Agent SpawnAgent(PlayerRef playerRef, Vector3 position, Quaternion rotation)
{
var player = Context.NetworkGame.GetPlayer(playerRef);
if (player.AgentPrefab.IsValid == false)
{
throw new InvalidOperationException(nameof(player.AgentPrefab));
}
var agentObject = Runner.Spawn(player.AgentPrefab, position, rotation, playerRef);
var agent = agentObject.GetComponent<Agent>();
Runner.SetPlayerAlwaysInterested(playerRef, agentObject, true);
var statistics = player.Statistics;
statistics.IsAlive = true;
statistics.RespawnTimer = default;
player.UpdateStatistics(statistics);
player.SetActiveAgent(agent);
return agent;
}
protected void FinishGameplay()
{
if (State != EState.Active)
return;
if (Runner.IsServer == false)
return;
State = EState.Finished;
Runner.SessionInfo.IsOpen = false;
Context.Backfill.BackfillEnabled = false;
if (Application.isBatchMode == true)
{
StartCoroutine(ShutdownCoroutine());
}
}
protected void SetSpectatorTargetToBestPlayer(Player spectatorPlayer)
{
var bestPlayer = PlayerRef.None;
int bestPosition = int.MaxValue;
foreach (var player in Context.NetworkGame.ActivePlayers)
{
if (player == null)
continue;
var statistics = player.Statistics;
if (statistics.IsEliminated == true)
continue;
int position = statistics.Position > 0 ? statistics.Position : 1000;
if (position < bestPosition)
{
bestPlayer = statistics.PlayerRef;
bestPosition = position;
}
}
spectatorPlayer.SetObservedPlayer(bestPlayer);
}
// PRIVATE METHODS
private void FixedUpdateNetwork_Active()
{
while (_respawnRequests.Count > 0)
{
var respawnRequest = _respawnRequests.Peek();
if (respawnRequest.Timer.Expired(Runner) == false)
break;
_respawnRequests.Dequeue();
Respawn(respawnRequest.PlayerRef);
}
_backfillTimerS += UnityEngine.Time.deltaTime;
if (_backfillTimerS > BackfillTimeLimit)
{
Context.Backfill.BackfillEnabled = false;
}
if (_endTimer.Expired(Runner) == true)
{
FinishGameplay();
}
}
private void FixedUpdateNetwork_Finished()
{
}
private void Respawn(PlayerRef playerRef)
{
var player = Context.NetworkGame.GetPlayer(playerRef);
if (player == null)
return; // Player is not present anymore
player.DespawnAgent();
TrySpawnAgent(player);
}
private void RecalculatePositions()
{
var allStatistics = ListPool.Get<PlayerStatistics>(byte.MaxValue);
foreach (var player in Context.NetworkGame.ActivePlayers)
{
if (player == null)
continue;
var statistics = player.Statistics;
if (statistics.IsValid == false)
continue;
allStatistics.Add(statistics);
}
SortPlayers(allStatistics);
for (int i = 0; i < allStatistics.Count; i++)
{
var statistics = allStatistics[i];
statistics.Position = (byte)(i + 1);
Context.NetworkGame.GetPlayer(statistics.PlayerRef).UpdateStatistics(statistics);
}
ListPool.Return(allStatistics);
}
private void OnShrinkingAreaAnnounced(Vector3 center, float radius)
{
_availableSpawnPoints.Clear();
var radiusSqr = radius * radius;
foreach (var spawnPoint in _allSpawnPoints)
{
if (spawnPoint.SpawnEnabled == false)
continue;
var direction = spawnPoint.transform.position - center;
direction.y = 0f;
if (direction.sqrMagnitude <= radiusSqr)
{
_availableSpawnPoints.Add(spawnPoint);
}
}
}
private IEnumerator StopGameCoroutine()
{
RPC_StopGame();
Context.Backfill.BackfillEnabled = false;
yield return new WaitForSecondsRealtime(0.25f);
Global.Networking.StopGame();
}
private IEnumerator ShutdownCoroutine()
{
yield return new WaitForSecondsRealtime(20.0f);
Debug.LogWarning("Shutting down...");
Application.Quit();
}
// RPCs
[Rpc(RpcSources.StateAuthority, RpcTargets.All, Channel = RpcChannel.Reliable)]
private void RPC_AgentDeath(KillData killData)
{
OnAgentDeath?.Invoke(killData);
}
[Rpc(RpcSources.StateAuthority, RpcTargets.All, Channel = RpcChannel.Reliable)]
private void RPC_PlayerEliminated(PlayerRef playerRef)
{
OnPlayerEliminated?.Invoke(playerRef);
}
[Rpc(RpcSources.StateAuthority, RpcTargets.All, Channel = RpcChannel.Reliable)]
private void RPC_PlayerJoinedGame(PlayerRef playerRef)
{
OnPlayerJoinedGame?.Invoke(playerRef);
}
[Rpc(RpcSources.StateAuthority, RpcTargets.All, Channel = RpcChannel.Reliable)]
private void RPC_PlayerLeftGame(PlayerRef playerRef, string nickname)
{
OnPlayerLeftGame?.Invoke(nickname);
}
[Rpc(RpcSources.StateAuthority, RpcTargets.Proxies, Channel = RpcChannel.Reliable)]
private void RPC_StopGame()
{
Global.Networking.StopGame(Networking.STATUS_SERVER_CLOSED);
}
// HELPERS
private class DefaultPlayerComparer : IComparer<PlayerStatistics>
{
public int Compare(PlayerStatistics x, PlayerStatistics y)
{
return y.Score.CompareTo(x.Score);
}
}
}
}