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.Text.RegularExpressions; using Unity.VisualScripting; /// /// 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 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 string arenaAssetsPath = "Assets/ScriptedAssets/Arenas"; private static string ruleAssetsPath = "Assets/ScriptedAssets/Rules"; 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 players participating in the match. /// public List matchPlayers { get; private set; } = new List(); /// /// 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(); } /// /// Update and check the match conditions in regards to the rules. /// /// A change in the matches progression. public void UpdateMatchCondition(MatchConditionUpdate update) { Player updatedPlayer = null; foreach (Player p in matchPlayers) { if (p.character.shipName == update.Ship.props.shipName) { updatedPlayer = p; Log.Debug($"Players: {p.name} match statistic will be updated."); } } // TODO: Match Result should contain progression over multiple rounds. MatchResult result = MatchLogic.UpdateMatchResult(updatedPlayer, update, matchPlayerStatistics); if (updatedPlayer != null && result != null) { matchState = MatchState.End; Log.Info("Match has ended, winner will be declared."); // TODO: Take player decisions into account before restarting the match // TODO: Include the statistics and match round progression in announcements AnnounceWinner(result); return; } Log.Error($"Ship: {update.Ship.props.shipName} does not belong to a player in this match." + " Can't update match."); } /// /// Loads the characters a player can choose from a fixed path /// in the project. /// private void LoadAvailableArenas() { string[] files = Directory.GetFiles(arenaAssetsPath, "*.asset", SearchOption.TopDirectoryOnly); foreach (var file in files) { availableArenas.Add(AssetDatabase.LoadAssetAtPath(file)); } } /// /// Loads the different rules/game modes which are available, /// at the fixed asset path. /// private void LoadAvailableRules() { string[] files = Directory.GetFiles(ruleAssetsPath, "*.asset", SearchOption.TopDirectoryOnly); foreach (var file in files) { availableRules.Add(AssetDatabase.LoadAssetAtPath(file)); } } /// /// Creates statistics for the players participating in the match. /// public void SetupMatchPlayerStatistics() { matchPlayerStatistics.Clear(); foreach (Player p in matchPlayers) { MatchPlayerStatistic mps = new MatchPlayerStatistic { IsOut = false, Lives = matchRule.lives, Score = matchRule.score, Time = matchRule.time }; matchPlayerStatistics.Add(p, mps); } } /// /// 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. /// TODO: Also restarts the match right now, no matter what. /// /// Result data of the completed match. async public void AnnounceWinner(MatchResult mr) { UIManager.G.announcments.QueueAnnounceText($"{mr.Winner.playerName}" + " has won the match!", 1.618f); await Tween.Delay(1.618f); ResetMatch(); UIManager.G.announcments.QueueAnnounceText("Restarting.", 0.3f); UIManager.G.announcments.QueueAnnounceText("Restarting..", 0.3f); UIManager.G.announcments.QueueAnnounceText("Restarting...", 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() { ResetMatchCharacters(); SetupMatchPlayerStatistics(); } /// /// Initializes the match, waits for match begin /// confirmation by the players and counts down to start. /// async public void StartMatch() { foreach (Player p in matchPlayers) { p.spawnedCharacter.transform.localPosition = arenaProperties.spawnPositions[p.playerNumber - 1]; } SetupMatchPlayerStatistics(); 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 matchPlayers) { p.spawnedCharacter.transform.localPosition = arenaProperties.spawnPositions[p.playerNumber - 1]; } SetupMatchPlayerStatistics(); matchState = MatchState.Match; } public void ConfirmMatchStart(TaskCompletionSource startPressed) { startPressed.TrySetResult(true); } /// /// Is called by the GameplayMetaInputEvents object, when someone pressed start. /// public void StartPressed() { OnStartPressed?.Invoke(); } public void PausePressed() { if (matchState == MatchState.Pause) { ContinueMatch(); UIManager.G.ShowPauseMenu(); return; } if (matchState != MatchState.Match) { Log.Info("Can only pause when match is in progress."); return; } matchState = MatchState.Pause; UIManager.G.HidePauseMenu(); } 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; } /// /// Spawns the characters which were chosen by the players /// and sets them up to be playable. /// /// List of players for this match public void SpawnCharacters(List players) { // TODO: This belongs in another intialization method GameObject arena = GameObject.Find("Arena Placement"); GameObject matchCamera = GameObject.Find("Match Camera"); if (arena == null) { Log.Error("ArenaPlacement not found in scenes. Cannot spawn players."); return; } if (matchCamera == null) { Log.Error("Match camera not found in scenes. Cannot initialize spawned Characters."); return; } foreach (Player p in players) { GameObject shipObject = Instantiate(p.character.shipObject); p.spawnedCharacter = shipObject; shipObject.TryGetComponent(out Ship ship); ship.state = new ShipHandling.ShipState(); ship.cameraOperator = matchCamera.GetComponent(); shipObject.transform.SetParent(arena.transform, false); shipObject.transform.localPosition = arenaProperties.spawnPositions[p.playerNumber - 1]; shipObject.transform.localScale = new Vector3(); Tween.Scale(shipObject.transform, new Vector3(0.7f, 0.7f, 0.7f), 1f); matchPlayers.Add(p); } } /// /// Resets the position, rotation and state of the spawned characters. /// public void ResetMatchCharacters() { foreach (Player mp in matchPlayers) { GameObject shipGO = mp.spawnedCharacter; shipGO.TryGetComponent(out Ship ship); ship.state.boostCapacity = ship.props.maxBoostCapacity; shipGO.transform.localPosition = arenaProperties.spawnPositions[mp.playerNumber - 1]; shipGO.transform.rotation = shipGO.transform.parent.rotation; } } } }