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;
}
}
}
}
///
/// 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();
StatisticsManager.G.ResetScore();
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);
MatchCamera.GetComponent().AddCharacter(shipGO);
ship.State.IsFrozen = false;
ship.State.BoostCapacity = ship.Props.MaxBoostCapacity;
ship.SetHealth(ship.Props.MaximumHealth);
ship.EquipWeapon(FORGE3D.WeaponEffect.None);
body.velocity = Vector3.zero;
shipGO.transform.localPosition =
ArenaProperties.spawnPositions[mp.playerNumber - 1];
shipGO.transform.rotation = shipGO.transform.parent.rotation;
}
}
}
}