using System; using System.Collections.Generic; using System.IO; using System.Reflection; using log4net; using PrimeTween; using UnityEditor; using UnityEngine; using GameLogic; using System.Threading.Tasks; using System.Linq; /// /// States the match can be in. /// public enum MatchState { CharacterSelect, Pause, Starting, Match, End } /// /// Update data relevant to this matches rules. /// public class MatchConditionUpdate { public WinCondition Condition { get; set; } public Ship Ship { get; set; } public int Count { get; set; } } /// /// The properties a player is assigned at the start /// of the match, according to the rules. /// public class MatchPlayerStatistic { public bool IsOut { get; set; } public int RoundsWon { get; set; } public int Lives { get; set; } public int Score { get; set; } public int Time { get; set; } } namespace Managers { /// /// Manages the initialization and progression of matches. /// public class MatchManager : MonoBehaviour { private static ILog Log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); private static readonly string arenaAssetsPath = "Assets/ScriptedAssets/Arenas"; private static readonly string ruleAssetsPath = "Assets/ScriptedAssets/Rules"; private static readonly string arenaAssetsBundleName = "arenas"; private static readonly string ruleAssetsBundleName = "rules"; private GameResult CurrentMatchResult; public GameObject MatchArena; public GameObject MatchCamera; public SceneEnum MatchScene; public event Action OnStartPressed; /// /// Globally accessible member to use manager with. /// public static MatchManager G { get; private set; } /// /// The current state of the match. /// public MatchState matchState = MatchState.CharacterSelect; /// /// The statistics regarding the current match mapped to the players. /// public Dictionary MatchPlayerStatistics { get; set; } = new Dictionary(); /// /// Bundle of properties regarding the arena. /// public Arena ArenaProperties { get; private set; } /// /// The rules under which this match takes place. /// public MatchRule MatchRule { get; private set; } /// /// List of all the arenas in the assets. /// public List AvailableArenas { get; private set; } = new List(); /// /// List of all the rules/game modes in the assets. /// public List AvailableRules { get; private set; } = new List(); void Awake() { G = this; Log.Info("Awake"); } void Start() { LoadAvailableRules(); LoadAvailableArenas(); } /// /// Loads the different rules/game modes from the projects assets. /// private void LoadAvailableRules() { #if UNITY_EDITOR GameManager.LoadAssetsFromFolder(ruleAssetsPath, AvailableRules); #else GameManager.LoadAssetsFromBundle(ruleAssetsBundleName, AvailableRules); #endif } /// /// Loads the characters a player can choose from the projects assets. /// private void LoadAvailableArenas() { #if UNITY_EDITOR GameManager.LoadAssetsFromFolder(arenaAssetsPath, AvailableArenas); #else GameManager.LoadAssetsFromBundle(arenaAssetsBundleName, AvailableArenas); #endif } /// /// Update and check the match conditions in regards to the rules. /// /// A change in the matches progression. public void UpdateMatchCondition(MatchConditionUpdate update) { // Quit updating when the round is over if (CurrentMatchResult.IsRoundWon) return; Player updatedPlayer = null; foreach (Player p in PlayerManager.G.MatchPlayers) { if (p.character.shipName == update.Ship.props.shipName) { updatedPlayer = p; Log.Debug($"Players: {p.playerName}'s match statistic will be updated."); break; } } if (updatedPlayer == null) { Log.Error($"Ship: {update.Ship.props.shipName} does not belong to a player in this match." + " Can't update match."); return; } if (CurrentMatchResult == null) { Log.Error("Match has no result statistics attached and was set up incorrectly!"); return; } MatchLogic.UpdateMatchResult(updatedPlayer, update, MatchPlayerStatistics, CurrentMatchResult); if (CurrentMatchResult.IsMatchWon) { Log.Info("Match has ended, winner will be declared."); AnnounceMatchWinner(CurrentMatchResult); return; } if (CurrentMatchResult.IsRoundWon) { Log.Info($"Round {CurrentMatchResult.RoundsPlayed} of {MatchRule.rounds} has ended." + $"{CurrentMatchResult.Winner?.playerName} won this round."); AnnounceRoundWinner(CurrentMatchResult); } else { foreach (var mps in MatchPlayerStatistics) { if (mps.Value.IsOut) { MatchCamera.GetComponent().RemoveCharacter(mps.Key.spawnedCharacter); mps.Key.spawnedCharacter.TryGetComponent(out Ship ship); ship.state.IsFrozen = true; ship.boostUI.SetIsOut(mps.Key); } } } } /// /// Creates statistics for the players participating in the match. /// public void SetupMatchPlayerStatistics() { MatchPlayerStatistics.Clear(); foreach (Player p in PlayerManager.G.MatchPlayers) { MatchPlayerStatistic mps = new MatchPlayerStatistic { IsOut = false, Lives = MatchRule.lives, Score = MatchRule.score, Time = MatchRule.time }; if (!MatchPlayerStatistics.TryAdd(p, mps)) { Log.Info($"Player {p.name} already has statistics set up."); } } } /// /// Just assigns new arena properties for now. /// TODO: Should initiate all the necessary steps upon switching the arena. /// /// Index of the arena (arenas scripted asset folder order from 0) public void LoadArenaProperties(int arenaIndex) { if (AvailableArenas.Count - 1 < arenaIndex) { Log.Error($"There are only: {AvailableArenas.Count} arenas loaded." + $" Couldn't load arena number: {arenaIndex + 1}"); return; } ArenaProperties = AvailableArenas[arenaIndex]; } /// /// Assigns rules to the match. /// /// Index of the rule to be loaded /// (rules scripted asset folder order from 0) public void LoadMatchRules(int ruleIndex) { if (AvailableArenas.Count - 1 < ruleIndex) { Log.Error($"There are only: {AvailableArenas.Count} rules loaded." + $" Couldn't load match rule number: {ruleIndex + 1}"); return; } MatchRule = AvailableRules[ruleIndex]; MatchLogic.currentRule = MatchRule; } /// /// Just sets the match state for now. /// TODO: maybe use in character selection /// public void StartCharacterSelect() { matchState = MatchState.CharacterSelect; } /// /// Announcement of who won the match. /// /// Result data of the completed match. async public void AnnounceMatchWinner(GameResult mr) { UIManager.G.Announcments.QueueAnnounceText($"{mr.Winner.playerName}" + " has won the match!", 2f); await Tween.Delay(2f * 0.33f); matchState = MatchState.End; await Tween.Delay(2f * 0.66f); UIManager.G.ShowMatchEndMenu(MatchArena.transform); MatchCamera.GetComponent().enabled = false; } async public void StartRematch() { MatchCamera.GetComponent().enabled = true; ResetMatch(); UIManager.G.Announcments.QueueAnnounceText("Starting rematch.", 0.3f); UIManager.G.Announcments.QueueAnnounceText("Starting rematch..", 0.3f); UIManager.G.Announcments.QueueAnnounceText("Starting rematch...", 0.3f); UIManager.G.Announcments.QueueAnnounceText("GO!", 0.5f); await Tween.Delay(0.9f); matchState = MatchState.Match; } /// /// Announcement of who won the Round. /// /// Result data of the completed match. async public void AnnounceRoundWinner(GameResult mr) { var winnerStats = MatchPlayerStatistics[mr.Winner]; // TODO: Confusing UIManager.G.Announcments.QueueAnnounceText($"{mr.Winner.playerName}" + " has won the Round! \n" + $"They won {winnerStats.RoundsWon} rounds \n need {MatchRule.rounds} to win.", 3f); await Tween.Delay(3f * 0.33f); matchState = MatchState.Pause; ResetRound(); await Tween.Delay(3f * 0.66f); ResetMatchCharacters(); UIManager.G.Announcments.QueueAnnounceText("Starting next round.", 0.3f); UIManager.G.Announcments.QueueAnnounceText("Starting next round..", 0.3f); UIManager.G.Announcments.QueueAnnounceText("Starting next round...", 0.3f); UIManager.G.Announcments.QueueAnnounceText("GO!", 0.5f); await Tween.Delay(0.9f); matchState = MatchState.Match; } /// /// Resets player positions and statistics. /// public void ResetMatch() { CurrentMatchResult = new GameResult(); ResetMatchCharacters(); SetupMatchPlayerStatistics(); } /// /// Resets the round statistics. /// public void ResetRound() { CurrentMatchResult.IsRoundWon = false; } /// /// Initializes the match, waits for match begin /// confirmation by the players and counts down to start. /// async public void StartMatch() { foreach (Player p in PlayerManager.G.MatchPlayers) { p.spawnedCharacter.transform.localPosition = ArenaProperties.spawnPositions[p.playerNumber - 1]; } SetupMatchPlayerStatistics(); CurrentMatchResult = new GameResult(); matchState = MatchState.Starting; UIManager.G.ShowMatchStartPrompt(); // Wait until someone pressed start var startPressed = new TaskCompletionSource(); OnStartPressed += () => ConfirmMatchStart(startPressed); // TODO: This could break in WebGL due to threading await startPressed.Task; OnStartPressed = null; UIManager.G.HideAnnouncement(); UIManager.G.Announcments.QueueAnnounceText("3", 1); UIManager.G.Announcments.QueueAnnounceText("2", 1); UIManager.G.Announcments.QueueAnnounceText("1", 1); UIManager.G.Announcments.QueueAnnounceText("GO!", 0.5f); await Tween.Delay(3); matchState = MatchState.Match; } public void StartTestMatch() { foreach (Player p in PlayerManager.G.MatchPlayers) { p.spawnedCharacter.transform.localPosition = ArenaProperties.spawnPositions[p.playerNumber - 1]; } SetupMatchPlayerStatistics(); CurrentMatchResult = new GameResult(); matchState = MatchState.Match; } public void ConfirmMatchStart(TaskCompletionSource startPressed) { startPressed.TrySetResult(true); } /// /// Is called by the GameplayMetaInputEvents object, when someone pressed start. /// public void StartPressed() { if (matchState != MatchState.Match) { OnStartPressed?.Invoke(); return; } } public void PausePressed() { // TODO: MetaInputEvent in InGameUI scene -> PausePressed here -> ShowPauseMenu in UIManager -> PauseMenu Show // When clicking continue in PauseMenu -> PausePressed here -> UIManager HidePause -> PauseMenu Hide if (matchState == MatchState.Pause) { ContinueMatch(); UIManager.G.HidePauseMenu(); MatchCamera.GetComponent().enabled = true; return; } if (matchState != MatchState.Match) { Log.Info("Can only pause when match is in progress."); return; } matchState = MatchState.Pause; UIManager.G.ShowPauseMenu(MatchCamera.transform); MatchCamera.GetComponent().enabled = false; } async private void ContinueMatch() { UIManager.G.Announcments.QueueAnnounceText("Get Ready.", 0.3f); UIManager.G.Announcments.QueueAnnounceText("Get Ready..", 0.3f); UIManager.G.Announcments.QueueAnnounceText("Get Ready...", 0.3f); UIManager.G.Announcments.QueueAnnounceText("GO!", 0.5f); await Tween.Delay(0.9f); matchState = MatchState.Match; } public void AssignMatchObjects() { MatchArena = GameObject.Find("Arena Placement"); MatchCamera = GameObject.Find("Match Camera"); } /// /// Resets the position, rotation and state of the spawned characters. /// public void ResetMatchCharacters() { foreach (Player mp in PlayerManager.G.MatchPlayers) { GameObject shipGO = mp.spawnedCharacter; shipGO.TryGetComponent(out Ship ship); shipGO.TryGetComponent(out Rigidbody body); ship.boostUI.SetIsIn(mp); MatchCamera.GetComponent().AddCharacter(shipGO); ship.state.IsFrozen = false; ship.state.boostCapacity = ship.props.maxBoostCapacity; body.velocity = Vector3.zero; shipGO.transform.localPosition = ArenaProperties.spawnPositions[mp.playerNumber - 1]; shipGO.transform.rotation = shipGO.transform.parent.rotation; } } } }