using System; using System.Collections; using System.Collections.Generic; using System.Linq; using Firebase.Database; using Firebase.Extensions; using Newtonsoft.Json; using TMPro; using Unity.VisualScripting; using UnityEditor.Localization; using UnityEngine; using UnityEngine.Localization.Settings; using UnityEngine.Localization.Tables; using UnityEngine.UI; /// /// This is the game state manager on the phone side /// public class GameManager : MonoBehaviour { private List players = new(); public Player currentPlayer = null; [Header("Other component")] public float explanationTime = 4f; public StringTableCollection stringTableCollection; [Header("Home Connection Component")] public TMP_InputField roomCodeField; public TextMeshProUGUI roomError; public TMP_InputField playerNameField; public TextMeshProUGUI nameError; public Button submitNewPlayer; [Header("WaitingRoom Component")] public TextMeshProUGUI listPlayersUI; public GameObject submitStartGame; [Header("Explanation Component")] public TextMeshProUGUI counterExplanation; [Header("MakeProposition Component")] public TextMeshProUGUI counterMakeProposition; [Header("Vote Component")] public TextMeshProUGUI counterVote; [Header("EndGame Components")] [SerializeField] private GameObject endGameFirstPlayer; [SerializeField] private GameObject endGameOtherPlayers; [Header("Pages")] public GameObject HomeConnection; public GameObject WaitingRoom; public GameObject BeforeStart; public GameObject TakePicture; public GameObject VotePicture; public GameObject WaitingOtherPlayers; public GameObject EndGame; internal Room myRoom; private DatabaseReference realtimeDB; private DatabaseReference myOnlineRoom; private DateTime endOfViewDate = DateTime.MinValue; /// /// Contains all the translated string for the UI /// private StringTable stringTable; private void Awake() { submitNewPlayer.interactable = false; FirebaseInitializer.Instance.onFirebaseReady += Initialize; } private void Start() { InitializeHomePage(); stringTable = stringTableCollection.GetTable(LocalizationSettings.SelectedLocale.Identifier) as StringTable; } private void InitializeHomePage() { HomeConnection.SetActive(true); WaitingRoom.SetActive(false); BeforeStart.SetActive(false); TakePicture.SetActive(false); VotePicture.SetActive(false); WaitingOtherPlayers.SetActive(false); EndGame.SetActive(false); submitStartGame.SetActive(false); submitNewPlayer.interactable = true; if (PlayerPrefs.HasKey("lastplayername")) playerNameField.text = PlayerPrefs.GetString("lastplayername"); } private void OnApplicationQuit() { if (myOnlineRoom == null) return; myOnlineRoom.Child("players").Child(currentPlayer.id).RemoveValueAsync().ContinueWithOnMainThread(task => { Debug.Log($"delete player {currentPlayer.name}"); if (myOnlineRoom != null) { myOnlineRoom.ValueChanged -= OnRoomUpdate; } }); } private void Update() { if (myRoom == null) return; if (myRoom.currentState == (int)GameState.Explanation && endOfViewDate != DateTime.MinValue) { TimeSpan duration = endOfViewDate - DateTime.Now; counterExplanation.text = ((int)duration.TotalSeconds).ToString("D1"); if (duration.TotalMilliseconds <= 0) { Debug.Log("It's time to make proposition !"); endOfViewDate = DateTime.MinValue; } } if (myRoom.currentState == (int)GameState.MakeProposition && endOfViewDate != DateTime.MinValue) { TimeSpan duration = endOfViewDate - DateTime.Now; counterMakeProposition.text = ((int)duration.TotalSeconds).ToString("D1"); if (duration.TotalMilliseconds <= 0) { Debug.Log("It's time to finish proposition !"); endOfViewDate = DateTime.MinValue; } } if (myRoom.currentState == (int)GameState.MakeVote && endOfViewDate != DateTime.MinValue) { TimeSpan duration = endOfViewDate - DateTime.Now; counterVote.text = ((int)duration.TotalSeconds).ToString("D1"); if (duration.TotalMilliseconds <= 0) { Debug.Log("It's time to finish proposition !"); endOfViewDate = DateTime.MinValue; } } } private void Initialize() { FirebaseInitializer.Instance.onFirebaseReady -= Initialize; realtimeDB = FirebaseDatabase.DefaultInstance.RootReference; submitNewPlayer.interactable = true; Debug.Log("Realtime DB initialized"); } /// /// Send your name and game room to the server /// public void PlayerValidateNameAndServerRoom(string _name, string _code) { nameError.gameObject.SetActive(false); roomError.gameObject.SetActive(false); if (string.IsNullOrEmpty(_name)) { Debug.LogError("Player name is empty", this); string errorText = stringTable.GetEntry("PhoneView/Canvas/Background/HomeState/NameField/ErrorNameLabel").LocalizedValue; nameError.text = errorText; nameError.gameObject.SetActive(true); submitNewPlayer.interactable = true; return; } if (string.IsNullOrEmpty(_code)) { Debug.LogError("Room code is empty", this); string errorText = stringTable.GetEntry("PhoneView/Canvas/Background/HomeState/NameField/EmptyRoom").LocalizedValue; roomError.text = errorText; roomError.gameObject.SetActive(true); submitNewPlayer.interactable = true; return; } currentPlayer = new Player(_name); PlayerPrefs.SetString("lastplayername", _name); PlayerPrefs.Save(); //check if the room exists, if not display an error message CheckIfRoomExists(_code, room => { if (room == null) { Debug.LogError("The room doesn't exists"); string errorText = stringTable.GetEntry("PhoneView/Canvas/Background/HomeState/NameField/ErrorRoom").LocalizedValue; roomError.text = errorText; roomError.gameObject.SetActive(true); submitNewPlayer.interactable = true; } else { myOnlineRoom = realtimeDB.Child("rooms").Child(_code); //subscribe to it myOnlineRoom.ValueChanged += OnRoomUpdate; //if room exists, join it JoinRoom(() => { myRoom.currentState = (int)GameState.WaitingForOtherPlayersToJoin; players.Add(currentPlayer); WaitingRoom.SetActive(true); HomeConnection.SetActive(false); }); } }); } private void CheckIfRoomExists(string _roomCode, Action callback_Room) { realtimeDB.Child("rooms").Child(_roomCode).GetValueAsync().ContinueWithOnMainThread(task => { if (task.IsFaulted) { Debug.LogException(task.Exception); } else if (task.IsCompleted) { DataSnapshot snapshot = task.Result; if (snapshot == null) { callback_Room?.Invoke(null); } else { callback_Room?.Invoke(JsonUtility.FromJson(snapshot.GetRawJsonValue())); } } }); } /// /// Add this player to the room /// private void JoinRoom(Action callback_OnRoomJoined) { string JSON = JsonUtility.ToJson(currentPlayer); Debug.Log(JSON); try { myOnlineRoom.Child("players").Child(currentPlayer.id).SetRawJsonValueAsync(JSON).ContinueWithOnMainThread(task => { if (task.IsFaulted) { Debug.LogException(task.Exception); } else { Debug.Log($"{currentPlayer.name} has been added to the room", this); callback_OnRoomJoined?.Invoke(); } }); } catch (Exception ex) { Debug.LogException(ex); } } /// /// Call this only by the first player /// public void StartGame() { // send Start Game myRoom.SetPlayersAreReady(1); string JSON = JsonUtility.ToJson(myRoom); Debug.Log(JSON); try { SendCurrentState(GameState.Explanation, () => { Debug.Log($"start the game", this); myRoom.currentState = (int)GameState.Explanation; WaitingRoom.SetActive(false); BeforeStart.SetActive(true); }); } catch (Exception ex) { Debug.LogException(ex); } } /// /// Automatically called when something change in your room /// private void OnRoomUpdate(object sender, ValueChangedEventArgs e) { GameState lastState = (GameState)myRoom.currentState; try { if (e?.Snapshot?.GetRawJsonValue() != null) { string JSON = e.Snapshot.GetRawJsonValue(); Debug.Log($"Room has been updated : {JSON}", this); myRoom = JsonConvert.DeserializeObject(e.Snapshot.GetRawJsonValue()); } } catch (Exception ex) { Debug.LogException(ex); } if (myRoom == null) { Debug.LogError("Got an update for an empty room.", this); return; } Debug.Log($"lasState = {lastState}. Currentstep = {(GameState)myRoom.currentState}", this); if (myRoom.currentState != (int)lastState) { OnNewGameState(); } //call this every time we are on this state switch (myRoom.currentState) { case (int)GameState.WaitingForOtherPlayersToJoin: { List players = myRoom.GetOrderedPlayerList(); UpdateDisplayedListUser(players); if (players.Count >= 3 && CheckIfIAmTheFirst(players)) { submitStartGame.SetActive(true); } break; } default: break; } } /// /// Call this when Game state change /// private void OnNewGameState() { switch (myRoom.currentState) { case (int)GameState.EnteringName: { InitializeHomePage(); break; } case (int)GameState.Explanation: { Debug.Log("Explanation time.", this); WaitingOtherPlayers.SetActive(false); TakePicture.SetActive(false); WaitingRoom.SetActive(false); VotePicture.SetActive(false); EndGame.SetActive(false); BeforeStart.SetActive(true); endOfViewDate = DateTime.Now.AddSeconds(4); break; } case (int)GameState.MakeProposition: { Debug.Log("It's photo time !", this); WaitingOtherPlayers.SetActive(false); BeforeStart.SetActive(false); WaitingRoom.SetActive(false); VotePicture.SetActive(false); EndGame.SetActive(false); TakePicture.SetActive(true); endOfViewDate = DateTime.Now.AddSeconds(60); break; } case (int)GameState.MakeVote: { Debug.Log("It's voting time !", this); WaitingOtherPlayers.SetActive(false); BeforeStart.SetActive(false); WaitingRoom.SetActive(false); TakePicture.SetActive(false); EndGame.SetActive(false); VotePicture.SetActive(true); endOfViewDate = DateTime.Now.AddSeconds(20); Debug.Log("subscribe to question ID"); myOnlineRoom.Child("currentQuestionId").ValueChanged += OnCurrentQuestionChanged; break; } case (int)GameState.Score: { Debug.Log("it's scoring time !", this); Debug.Log("unsubscribe to question ID"); myOnlineRoom.Child("currentQuestionId").ValueChanged -= OnCurrentQuestionChanged; WaitingOtherPlayers.SetActive(false); BeforeStart.SetActive(false); WaitingRoom.SetActive(false); TakePicture.SetActive(false); VotePicture.SetActive(false); EndGame.SetActive(true); endGameFirstPlayer.SetActive(CheckIfIAmTheFirst(myRoom.GetPlayerList())); endGameOtherPlayers.SetActive(!CheckIfIAmTheFirst(myRoom.GetPlayerList())); break; } } } /// /// Call this to wait for other players /// public void WaitForPlayers() { if (TakePicture.activeInHierarchy) { TakePicture.SetActive(false); } if (VotePicture.activeInHierarchy) { VotePicture.SetActive(false); } WaitingOtherPlayers.SetActive(true); } private void UpdateDisplayedListUser(List players) { listPlayersUI.text = string.Empty; for (int i = 0; i < players.Count; i++) { listPlayersUI.text += "\n" + players[i].name; } } /// /// return true if you are the fist player /// /// /// private bool CheckIfIAmTheFirst(List players) { bool isFirst = false; if (players.Count > 1) { List sortedList = players.OrderBy(x => x.creationDate).ToList(); if (sortedList[0].id == currentPlayer.id) { isFirst = true; } } return isFirst; } public void SendCurrentState(GameState state, Action callback_oncCurrentStateSent) { myOnlineRoom.Child("currentState").SetValueAsync((int)state).ContinueWithOnMainThread(task => { if (task.IsFaulted) { Debug.LogException(task.Exception); } else { callback_oncCurrentStateSent?.Invoke(); } }); ; } public void OnClickSubmitSignIn() { string playerName = playerNameField.text; string roomCode = roomCodeField.text; submitNewPlayer.interactable = false; PlayerValidateNameAndServerRoom(playerName, roomCode); } public void onClickSamePlayers() { Debug.Log("Play with same players !", this); Room newRoom = myRoom.Copy(); newRoom.currentState = (int)GameState.Explanation; newRoom.questions.Clear(); newRoom.currentQuestionId = 0; string json = JsonConvert.SerializeObject(newRoom); myOnlineRoom.SetRawJsonValueAsync(json).ContinueWithOnMainThread(task => { if (task.IsFaulted) { Debug.LogException(task.Exception); } }); } public void onClickNewPlayers() { Debug.Log("Play with new players !", this); Room newRoom = myRoom.Copy(); newRoom.currentState = (int)GameState.EnteringName; newRoom.currentQuestionId = 0; newRoom.questions = null; newRoom.players = null; string json = JsonConvert.SerializeObject(newRoom); myOnlineRoom.SetRawJsonValueAsync(json).ContinueWithOnMainThread(task => { if (task.IsFaulted) { Debug.LogException(task.Exception); } }); } private void OnCurrentQuestionChanged(object sender, ValueChangedEventArgs onlineValue) { if (onlineValue == null || onlineValue.Snapshot == null || onlineValue.Snapshot.Value == null || myRoom.questions == null || myRoom.questions.Count == 0) return; int questionId = onlineValue.Snapshot.Value.ConvertTo(); Debug.Log($"new question ! Q{(onlineValue.Snapshot.Value.ConvertTo())}"); Question q = myRoom.questions[questionId]; //do not vote for your question if (myRoom.GetQuestionsByPlayer(currentPlayer).Contains(q)) { WaitingOtherPlayers.SetActive(true); VotePicture.SetActive(false); } else { VotePicture.SetActive(true); WaitingOtherPlayers.SetActive(false); endOfViewDate = DateTime.Now.AddSeconds(20); VotePicture.GetComponent().ShowPlayersProposition(q); } } /// /// Call this function from the vote button in the scene /// /// public void OnClickProposition(int propositionNumber) { /* Debug.Log($"Room has {myRoom.questions.Count} questions. the current Question is Q({myRoom.currentQuestionId})."); Debug.Log($"Q({myRoom.currentQuestionId}) has {myRoom.questions[myRoom.currentQuestionId].propositions.Count} propositions."); Debug.Log($"Player click on proposition {propositionNumber}."); Debug.Log($"it has {myRoom.questions[myRoom.currentQuestionId].propositions[propositionNumber].voters.Count} voters."); */ List voters = myRoom.questions[myRoom.currentQuestionId].propositions[propositionNumber].voters; if (voters == null) voters = new List(); voters.Add(currentPlayer.id); myOnlineRoom.Child("questions").Child(myRoom.currentQuestionId.ToString()).Child("propositions").Child(propositionNumber.ToString()).Child("voters").SetValueAsync(voters); WaitingOtherPlayers.SetActive(true); VotePicture.SetActive(false); } } public enum GameState { EnteringName = 0, WaitingForOtherPlayersToJoin = 1, Explanation = 2, MakeProposition = 3, MakeVote = 4, Score = 5 }