454 lines
13 KiB
C#
454 lines
13 KiB
C#
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;
|
|
using System.Linq;
|
|
|
|
/// <summary>
|
|
/// States the match can be in.
|
|
/// </summary>
|
|
public enum MatchState
|
|
{
|
|
CharacterSelect,
|
|
Pause,
|
|
Starting,
|
|
Match,
|
|
End
|
|
}
|
|
|
|
/// <summary>
|
|
/// Update data relevant to this matches rules.
|
|
/// </summary>
|
|
public class MatchConditionUpdate
|
|
{
|
|
public WinCondition Condition { get; set; }
|
|
public Ship Ship { get; set; }
|
|
public int Count { get; set; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// The properties a player is assigned at the start
|
|
/// of the match, according to the rules.
|
|
/// </summary>
|
|
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
|
|
{
|
|
/// <summary>
|
|
/// Manages the initialization and progression of matches.
|
|
/// </summary>
|
|
public class MatchManager : MonoBehaviour
|
|
{
|
|
private static ILog Log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
|
|
|
|
private static readonly string arenaAssetsBundleName = "arenas";
|
|
private static readonly string ruleAssetsBundleName = "rules";
|
|
private static readonly string arenaAssetsPath = "Assets/ScriptedAssets/Arenas";
|
|
private static readonly string ruleAssetsPath = "Assets/ScriptedAssets/Rules";
|
|
private GameResult CurrentMatchResult;
|
|
|
|
public GameObject MatchArena;
|
|
public GameObject MatchCamera;
|
|
|
|
public event Action OnStartPressed;
|
|
/// <summary>
|
|
/// Globally accessible member to use manager with.
|
|
/// </summary>
|
|
public static MatchManager G { get; private set; }
|
|
/// <summary>
|
|
/// The current state of the match.
|
|
/// </summary>
|
|
public MatchState matchState = MatchState.CharacterSelect;
|
|
/// <summary>
|
|
/// The statistics regarding the current match mapped to the players.
|
|
/// </summary>
|
|
public Dictionary<Player, MatchPlayerStatistic> MatchPlayerStatistics { get; set; } =
|
|
new Dictionary<Player, MatchPlayerStatistic>();
|
|
/// <summary>
|
|
/// Bundle of properties regarding the arena.
|
|
/// </summary>
|
|
public Arena ArenaProperties { get; private set; }
|
|
/// <summary>
|
|
/// The rules under which this match takes place.
|
|
/// </summary>
|
|
public MatchRule MatchRule { get; private set; }
|
|
/// <summary>
|
|
/// List of all the arenas in the assets.
|
|
/// </summary>
|
|
public List<Arena> AvailableArenas { get; private set; } = new List<Arena>();
|
|
/// <summary>
|
|
/// List of all the rules/game modes in the assets.
|
|
/// </summary>
|
|
public List<MatchRule> AvailableRules { get; private set; } = new List<MatchRule>();
|
|
|
|
|
|
void Awake()
|
|
{
|
|
G = this;
|
|
Log.Info("Awake");
|
|
}
|
|
|
|
void Start()
|
|
{
|
|
LoadAvailableRules();
|
|
LoadAvailableArenas();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Loads the different rules/game modes which are available,
|
|
/// at the fixed asset path.
|
|
/// </summary>
|
|
private void LoadAvailableRules()
|
|
{
|
|
#if UNITY_EDITOR
|
|
string[] files = Directory.GetFiles(arenaAssetsPath, "*.asset",
|
|
SearchOption.TopDirectoryOnly);
|
|
foreach (var file in files)
|
|
{
|
|
AvailableArenas.Add(AssetDatabase.LoadAssetAtPath<Arena>(file));
|
|
}
|
|
#else
|
|
var ruleAssetBundle = AssetBundle.LoadFromFile(Path.Combine(Application.streamingAssetsPath, ruleAssetsBundleName));
|
|
if (ruleAssetBundle == null)
|
|
{
|
|
Log.Error("Failed to load rules asset bundle!");
|
|
return;
|
|
}
|
|
AvailableRules.AddRange(ruleAssetBundle.LoadAllAssets<MatchRule>());
|
|
#endif
|
|
}
|
|
|
|
/// <summary>
|
|
/// Loads the characters a player can choose from a fixed path
|
|
/// in the project.
|
|
/// </summary>
|
|
private void LoadAvailableArenas()
|
|
{
|
|
#if UNITY_EDITOR
|
|
string[] files = Directory.GetFiles(ruleAssetsPath, "*.asset",
|
|
SearchOption.TopDirectoryOnly);
|
|
foreach (var file in files)
|
|
{
|
|
AvailableRules.Add(AssetDatabase.LoadAssetAtPath<MatchRule>(file));
|
|
}
|
|
#else
|
|
var arenasAssetBundle = AssetBundle.LoadFromFile(Path.Combine(Application.streamingAssetsPath, arenaAssetsBundleName));
|
|
if (arenasAssetBundle == null)
|
|
{
|
|
Log.Error("Failed to load arenas asset bundle!");
|
|
return;
|
|
}
|
|
AvailableArenas.AddRange(arenasAssetBundle.LoadAllAssets<Arena>());
|
|
#endif
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Update and check the match conditions in regards to the rules.
|
|
/// </summary>
|
|
/// <param name="update">A change in the matches progression.</param>
|
|
public void UpdateMatchCondition(MatchConditionUpdate update)
|
|
{
|
|
Player updatedPlayer = null;
|
|
foreach (Player p in PlayerManager.G.MatchPlayers)
|
|
{
|
|
if (p.character.shipName == update.Ship.props.shipName)
|
|
{
|
|
updatedPlayer = p;
|
|
Log.Debug($"Players: {p.name} match statistic will be updated.");
|
|
}
|
|
}
|
|
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);
|
|
}
|
|
else
|
|
{
|
|
Log.Info($"Round {CurrentMatchResult.RoundsPlayed} of {MatchRule.rounds} has ended." +
|
|
$"{CurrentMatchResult.Winner?.name} won this round.");
|
|
AnnounceRoundWinner(CurrentMatchResult);
|
|
}
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Creates statistics for the players participating in the match.
|
|
/// </summary>
|
|
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.");
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Just assigns new arena properties for now.
|
|
/// TODO: Should initiate all the necessary steps upon switching the arena.
|
|
/// </summary>
|
|
/// <param name="arenaIndex">Index of the arena (arenas scripted asset folder order from 0)</param>
|
|
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];
|
|
}
|
|
|
|
/// <summary>
|
|
/// Assigns rules to the match.
|
|
/// </summary>
|
|
/// <param name="ruleIndex">Index of the rule to be loaded
|
|
/// (rules scripted asset folder order from 0)</param>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Just sets the match state for now.
|
|
/// TODO: maybe use in character selection
|
|
/// </summary>
|
|
public void StartCharacterSelect()
|
|
{
|
|
matchState = MatchState.CharacterSelect;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Announcement of who won the match.
|
|
/// </summary>
|
|
/// <param name="mr">Result data of the completed match.</param>
|
|
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);
|
|
|
|
}
|
|
|
|
async public void StartRematch()
|
|
{
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Announcement of who won the Round.
|
|
/// </summary>
|
|
/// <param name="mr">Result data of the completed match.</param>
|
|
async public void AnnounceRoundWinner(GameResult mr)
|
|
{
|
|
var winnerStats = MatchPlayerStatistics[mr.Winner];
|
|
UIManager.G.Announcments.QueueAnnounceText($"{mr.Winner.playerName}" +
|
|
" has won the Round! \n" +
|
|
$"They won {winnerStats.RoundsWon} out of {MatchRule.rounds}.",
|
|
1.618f);
|
|
|
|
await Tween.Delay(1.618f * 0.33f);
|
|
matchState = MatchState.Pause;
|
|
await Tween.Delay(1.618f * 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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Resets player positions and statistics.
|
|
/// </summary>
|
|
public void ResetMatch()
|
|
{
|
|
CurrentMatchResult = new GameResult();
|
|
ResetMatchCharacters();
|
|
SetupMatchPlayerStatistics();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Initializes the match, waits for match begin
|
|
/// confirmation by the players and counts down to start.
|
|
/// </summary>
|
|
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<bool>();
|
|
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<bool> startPressed)
|
|
{
|
|
startPressed.TrySetResult(true);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Is called by the GameplayMetaInputEvents object, when someone pressed start.
|
|
/// </summary>
|
|
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.SetActive(true);
|
|
return;
|
|
}
|
|
if (matchState != MatchState.Match)
|
|
{
|
|
Log.Info("Can only pause when match is in progress.");
|
|
return;
|
|
}
|
|
matchState = MatchState.Pause;
|
|
UIManager.G.ShowPauseMenu(MatchArena.transform);
|
|
MatchCamera.SetActive(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");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Resets the position, rotation and state of the spawned characters.
|
|
/// </summary>
|
|
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.state.boostCapacity = ship.props.maxBoostCapacity;
|
|
|
|
body.velocity = Vector3.zero;
|
|
|
|
shipGO.transform.localPosition =
|
|
ArenaProperties.spawnPositions[mp.playerNumber - 1];
|
|
shipGO.transform.rotation = shipGO.transform.parent.rotation;
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|