using System; using System.Collections; using System.Collections.Generic; using System.Linq; using Unity.Netcode; using Unity.VisualScripting; using UnityEngine; using Object = UnityEngine.Object; using Random = UnityEngine.Random; namespace Game { public class GameManager : NetworkBehaviour { public BalanceValues balanceValues; public Object ballPrefab; public Object playerPrefab; public Object borderZonePrefab; public Object modificationPrefab; public ModificationProperties[] modifications; public Object wormholePrefab; public Object newBallPrefab; public static GameManager Singleton { get; private set; } public static BalanceValues BalanceValues => Singleton.balanceValues; public List Balls { get; } = new(); public Player Player1 { get; private set; } public Player Player2 { get; private set; } private void Awake() { Singleton = this; SetupPlayers(); } private void OnEnable() { // Move this to settings Application.targetFrameRate = 144; QualitySettings.vSyncCount = 0; } private IEnumerator SpawnLoop(Spawnable spawnable) { SpawnRate required = RoomSettings.SpawnRates[spawnable]; if (required == SpawnRate.Never) yield break; float baseSeconds = balanceValues.spawnRates.First(s => s.spawnable == spawnable).values.First(v => v.spawnRate == required).baseSeconds; while (Application.isPlaying) { yield return new WaitForSeconds(baseSeconds); switch (spawnable) { case Spawnable.NewBallTemporary: SpawnNewBall(false); break; case Spawnable.NewBallPermanent: SpawnNewBall(true); break; case Spawnable.Modification: SpawnModification(); break; case Spawnable.Wormhole: SpawnWormhole(); break; } } } private void Start() { SpawnBall(Player1, true); StartCoroutine(SpawnLoop(Spawnable.NewBallTemporary)); StartCoroutine(SpawnLoop(Spawnable.NewBallPermanent)); StartCoroutine(SpawnLoop(Spawnable.Modification)); StartCoroutine(SpawnLoop(Spawnable.Wormhole)); } private void SetupPlayers() { var p1Obj = Instantiate(playerPrefab); var p2Obj = Instantiate(playerPrefab); Player p1, p2; switch (RoomSettings.Type) { case Type.AI: p1 = p1Obj.AddComponent(); p2 = p2Obj.AddComponent(); ((AIPlayer) p1).Difficulty = RoomSettings.AIDifficulty; ((AIPlayer) p2).Difficulty = RoomSettings.AIDifficulty; break; case Type.RealOnline: p1 = p1Obj.AddComponent(); p2 = p2Obj.AddComponent(); ((RealPlayer) p1).isThisClient = true; break; case Type.RealOffline: p1 = p1Obj.AddComponent(); p2 = p2Obj.AddComponent(); ((RealPlayer) p1).isThisClient = true; ((RealPlayer) p2).isThisClient = true; break; case Type.Hybrid: p1 = p1Obj.AddComponent(); p2 = p2Obj.AddComponent(); ((RealPlayer) p1).isThisClient = true; ((AIPlayer) p2).Difficulty = RoomSettings.AIDifficulty; break; default: throw new ArgumentOutOfRangeException(); } p1.Side = Side.Bottom; p2.Side = Side.Top; Player1 = p1; Player2 = p2; p1.borderZonePrefab = p2.borderZonePrefab = borderZonePrefab; } private Vector2 GetSpawnPosition() { Vector2 area = balanceValues.spawnArea * Dimensions.Singleton.playGroundSize * 0.5f; float x = Random.Range(-area.x, area.x); float y = Random.Range(-area.y, area.y); return new Vector2(x, y); } private void SpawnNewBall(bool permanent) { Instantiate(newBallPrefab, GetSpawnPosition(), Quaternion.identity).GetComponent().IsPermanent = permanent; } private void SpawnWormhole() { Instantiate(wormholePrefab, GetSpawnPosition(), Quaternion.identity); } private void SpawnModification() { var properties = modifications[Random.Range(0, modifications.Length)]; var mod = Instantiate(modificationPrefab, GetSpawnPosition(), Quaternion.identity).GetComponent(); mod.Properties = properties; } public void SpawnBall(Player towards, bool isPermanent) { float startSpeed = balanceValues.ballSpeed; var position = new Vector2(0, -towards.transform.position.y * 0.5f); var ball = Instantiate(ballPrefab, position, Quaternion.identity).GetComponent(); ball.Rb.velocity = RandomDirectionTowards(towards) * startSpeed; ball.IsPermanent = isPermanent; ball.Radius = 0.5f; Balls.Add(ball); } private static Vector2 RandomDirectionTowards(Player player) { const float maxAngle = 45; var radians = Random.Range(-maxAngle, maxAngle) * Mathf.PI / 180; var x = Mathf.Sin(radians); var y = Mathf.Cos(radians) * player.Side switch { Side.Top => 1, Side.Bottom => -1, _ => throw new ArgumentOutOfRangeException() }; return new Vector2(x, y); } public void RemoveBall(Ball ball) { Balls.Remove(ball); Destroy(ball.gameObject); } } }