using System.Collections; using System.Collections.Generic; using System.Linq; using UnityEngine; using UnityEngine.Networking; using Networking.Server; using Networking; using UnityEngine.SceneManagement; public class GameManager : MonoBehaviour { #region Inspector Field [Header("Settings")] [SerializeField] private float AnimationTime; [SerializeField] private GameModeReference CurrentGameMode; [Header("References")] [SerializeField] [Tooltip("Prefab of character for players to play")] private Character characterPrefab; [SerializeField] private ServerObject server; [SerializeField] private ClientList ClientList; public blockSpawn spawnBlock; #endregion Inspector Field #region Private Variables public Dictionary playerData; private ActiveBlock[] EnvironmentBlocks; #endregion Private Variables #region Read Only /// /// Easy access to IEnumerable in playerData so we can Enumerate through it /// private PlayerData[] playerDataAsArray { get { return playerData.Values.ToArray(); } } /// /// Easy access to GameMode value in CurrentGameMode reference /// private GameMode gameMode { get { return CurrentGameMode.Value; } } #endregion Read Only #region Unity Functions private void Start() { spawnBlock = gameObject.GetComponent(); StartCoroutine(GameRoutine()); } private void Update() { server.ServerUpdate(); } private void OnEnable() { //Let Server know we want to recieve some messages RegisterHandlers(); } private void OnDisable() { //Let server know to not send messages this way UnRegisterHandlers(); } #endregion Unity Functions #region Class Functions private IEnumerator GameRoutine() { //Allows game mode to instantiate anything it might need; gameMode.PreGameStart(); //Spawn Characters and tell let the GameMode do anything with the characters it might want SpawnCharacters(); playerDataAsArray.ForEach(p => p.client.SendLives()); gameMode.GameStart(playerDataAsArray); playerData = playerData.OrderBy(unit => unit.Value.character.CurrentBlock.transform.position.x).ToDictionary(unit => unit.Key, unit => unit.Value); Dictionary filteredPlayers = playerData.Where(p => !p.Value.isDead).ToDictionary(unit => unit.Key, unit => unit.Value); int order = 1; foreach (PlayerData data in filteredPlayers.Values) { data.character.runOrder = order; order++; } //Loop until the GameMode lets us know the game is over while (!gameMode.isGameOver(playerDataAsArray)) { int playersalive_left = 0; int spotinarray = 0; int players = 0; foreach (PlayerData data in playerDataAsArray) { players++; if (data.client.Lives > 0) { playersalive_left++; spotinarray = players; } } if(playersalive_left == 1) { GlobalVariables.winneranimal = playerDataAsArray[spotinarray-1].client.characterAnimal; GlobalVariables.winnername = playerDataAsArray[spotinarray-1].client.Name; SceneManager.LoadScene("Winner"); } //wait until we have recieved all player input yield return StartCoroutine(WaitForPlayerInput()); //I hate having to do this EnvironmentBlocks = FindObjectsOfType(); //Routine for players movement yield return StartCoroutine(RoundRoutine()); //it's pretty long so it gets it's own coroutine; } //Let the gamemode know that the game is over gameMode.GameEnd(playerDataAsArray); } private void removePlayer(PlayerData player) { player.client.ChangeScene("WaitScene"); player.isDead = true; } private IEnumerator RoundRoutine() { //Tell the gamemode that we are starting a round gameMode.RoundStart(playerDataAsArray); //Loop until all players have finished moving while (playerDataAsArray.Any(p => !p.blockReader.Finished)) { //Loop through all players foreach (PlayerData player in playerDataAsArray) { yield return StartCoroutine(MoveRoutine(player));//Move Player gameMode.PlayerMoved(player);//LetGameModeKnow } //Let Gamemode know all players have moved gameMode.AllPlayersMoved(playerDataAsArray); //I hate having to do this EnvironmentBlocks = FindObjectsOfType(); //Let the environment take a turn yield return StartCoroutine(EnvironmentTurn()); gameMode.EnvironmentTurn(playerDataAsArray); //if Game is over break out of loop if (gameMode.isGameOver(playerDataAsArray)) break; } //decrease lives here!! foreach (PlayerData player in playerDataAsArray) { gameMode.PlayerKilled(player); } //Let GameMode know that Round is Over yield return StartCoroutine(EnvironmentEnd()); gameMode.RoundEnd(playerDataAsArray.ToArray()); playerDataAsArray.ForEach(p => p.client.SendLives()); //Update the players score //check is anyone has 0 lives remaining //remove them from the array //force them back to the waiting for players screen foreach (PlayerData player in playerDataAsArray) { if (player.client.Lives == 0) { removePlayer(player); } } //spawn collectible logic blocks spawnBlock.Spawn(); //Reset some player Data foreach (PlayerData player in playerDataAsArray) { if (player.client.Lives > 0) { player.blockReader.Reset(); player.client.SendInventory(); } } playerData = playerData.OrderBy(unit => unit.Value.character.CurrentBlock.transform.position.x).ToDictionary(unit => unit.Key, unit => unit.Value); Dictionary filteredPlayers = playerData.Where(p => !p.Value.isDead).ToDictionary(unit => unit.Key, unit => unit.Value); int order = 1; foreach (PlayerData data in filteredPlayers.Values) { data.character.runOrder = order; order++; } } private IEnumerator WaitForPlayerInput() { //send round length to players //#TODO make this only happen after first input LogicProtocols.FloatMsg RoundTime = new LogicProtocols.FloatMsg(gameMode.GetRoundTime()); foreach (PlayerData player in playerDataAsArray) { if (player.client.Lives == 0) { removePlayer(player); } } //playerDataAsArray.ForEach(p => p.client.conn.Send(LogicProtocols.SendRoundTime, RoundTime)); foreach (PlayerData player in playerDataAsArray) { if (player.client.Lives > 0) { player.client.conn.Send(LogicProtocols.SendRoundTime, RoundTime); } } //Send players to input Scene //ClientList.ForEach(p => p.ChangeScene("ClientScene")); foreach (ClientData client in ClientList) { if (client.Lives > 0) { client.ChangeScene("ClientScene"); } } //Let gamemode know clients are input-ing gameMode.InputStart(playerDataAsArray); //wait for all players to yield return new WaitUntil(() => playerData.All(p => p.Value.recievedList || p.Value.isDead || p.Value.character.lives == 0)); //reset //playerDataAsArray.ForEach(p => p.recievedList = false); //reset all players list foreach (PlayerData player in playerDataAsArray) { if (player.client.Lives > 0) { player.recievedList = false; } } //Let gamemode know all inputs have been recieved gameMode.InputEnd(playerDataAsArray); } private IEnumerator MoveRoutine(PlayerData data) { //If the current block hasn't already been removed, remove it from the player inventory //We need to check if it has already been removed so loops don't eat an entire stack if (data.blockReader.CurrentBlock != null && !data.blockReader.CurrentBlock.hasBeenRemoved) { data.client.Inventory.Remove(data.blockReader.CurrentBlock); data.blockReader.CurrentBlock.hasBeenRemoved = true; } yield return StartCoroutine(data.blockReader.Read(data.character, AnimationTime)); } private void SpawnCharacters() { playerData = new Dictionary(); Block[] SpawnBlocks = FindObjectsOfType().Where(p => p.isSpawnable).ToArray(); //int spawnIndex = 0; //If we have an odd number of players, then we start at spawn point 0 (in the middle) //If we have an even number, then we skip it //int spawnIndex = ((ClientList.Count() + 1) % 2); int spawnIndex = 0; foreach (ClientData client in ClientList) { Character newChar = Instantiate(characterPrefab); Block startingBlock = SpawnBlocks[spawnIndex++]; newChar.Initialise(startingBlock, client.Inventory, client.characterAnimal); newChar.transform.forward = startingBlock.SpawnDirection.ToVector(); playerData.Add(client.ID, new PlayerData(newChar, client)); newChar.ClientLink = client; client.playerCharacter = newChar; } } private IEnumerator EnvironmentTurn() { foreach (var InitiativeGroup in EnvironmentBlocks.GroupBy(p => p.GetInitative())){ foreach (ActiveBlock block in InitiativeGroup) StartCoroutine(block.OnEnvironmentTurn(playerDataAsArray)); yield return new WaitUntil(() => InitiativeGroup.All(p => p.isFinished)); InitiativeGroup.ForEach(p => p.Reset()); } } private IEnumerator EnvironmentEnd() { foreach (var InitiativeGroup in EnvironmentBlocks.GroupBy(p => p.GetInitative())){ foreach (ActiveBlock block in InitiativeGroup) StartCoroutine(block.OnRoundEnd(playerDataAsArray)); yield return new WaitUntil(() => InitiativeGroup.All(p => p.isFinished)); InitiativeGroup.ForEach(p => p.Reset()); } } private ActiveBlock[] GetActiveBlocksOf(int intiative) { return EnvironmentBlocks.Where(p => p.GetInitative() == intiative).ToArray(); } #endregion Class Functions #region Networking Functions /// /// Registers functions which should deal with incoming messages from clients /// private void RegisterHandlers() { server.RegisterHandler(LogicProtocols.SendLogicList, RecieveLogicList); } /// /// Clears any functions from server register which the manager would deal with /// private void UnRegisterHandlers() { server.UnregisterHandler(LogicProtocols.SendLogicList); } /// /// Called when server recieves moves from client /// /// messages passed by server private void RecieveLogicList(NetworkMessage msg) { //try to read base message as a logic message LogicProtocols.LogicMsg logicMsg; if (!msg.TryRead(out logicMsg)) return; //Update player Data with recieved list playerData[msg.conn.Hash()].blockReader.LogicChain = new List(logicMsg.elements); playerData[msg.conn.Hash()].recievedList = true; } #endregion Networking Functions } public class PlayerData { public Character character; public BlockReader blockReader; public ClientData client; public bool recievedList; public bool isDead = false; public PlayerData(Character character, ClientData client) { this.character = character; this.client = client; blockReader = new BlockReader(); } }