using ExitGames.Client.Photon; using Photon.Pun; using Photon.Realtime; using System.Collections.Generic; using System.Linq; using UnityEngine; using UnityEngine.EventSystems; public class Game : MonoBehaviourPunCallbacks { //Singleton public static Game Instance { get; private set; } //General information [Header("Game flow")] [Tooltip("Play time of the game in minutes")] public float playTime; [Tooltip("Length of overtime in seconds")] public float overtime; [Tooltip("Tick rate in ticks/second")] public float tickRate; [Header("Spawn Information")] public GameObject[] circuitSpawns; public float circuitScale; public float connectionScale; [Header("Prefabs")] public List circuitPrefabs; public GameObject cablePrefab; public GameObject chargePrefab; //Temporary dragged [HideInInspector] public Circuit draggedCircuit; [HideInInspector] public Connection draggedConnection; [HideInInspector] public Connection draggedCutter; //Necessary references private PhotonView pv; public Camera cam; //Objects public List Circuits { get; } = new List(); private readonly List circuitsToRemove = new List(); public List Connections { get; } = new List(); private readonly List connectionsToRemove = new List(); //Called by Unity void Awake() { Instance = this; pv = GetComponent(); } void Start() { if (!PhotonNetwork.IsConnected) { PhotonNetwork.OfflineMode = true; PhotonNetwork.CreateRoom("development"); PhotonNetwork.LocalPlayer.SetCustomProperties(new Hashtable() { ["ColorIndex"] = Random.Range(1, 9) }); StartGame(); } } void Update() { if (Input.GetMouseButtonDown(0) && !EventSystem.current.IsPointerOverGameObject()) OnLeftPressed(); if (Input.GetMouseButtonDown(1) && !EventSystem.current.IsPointerOverGameObject()) OnRightPressed(); else if (Input.GetMouseButtonUp(0)) OnLeftReleased(); else if (Input.GetMouseButton(0)) MoveDragged(); if (IsPlaying) UpdateGame(); } //Events and input controls public void OnDragStopped() { if (draggedCircuit != null) { Destroy(draggedCircuit.gameObject); draggedCircuit = null; } if (draggedConnection != null) { Destroy(draggedConnection.gameObject); draggedConnection = null; } if (draggedCutter != null) { Destroy(draggedCutter.gameObject); draggedCutter = null; } } private void OnLeftPressed() { if (!Circuits.Exists(c => c.IsMouseOver) && IsPlaying) { if (ScreenToPlaneRayCast(Input.mousePosition, out Vector3 hitPoint)) SpawnDraggableCutter(hitPoint); } } private void OnRightPressed() { OnDragStopped(); } private void OnLeftReleased() { if (draggedCircuit != null) { if (!draggedCircuit.IsNeedingReplace && !EventSystem.current.IsPointerOverGameObject()) { Vector3 pos = draggedCircuit.transform.position; CircuitType type = draggedCircuit.type; if (SpawnGameCircuit(pos, type)) { draggedCircuit.uICircuitCount.Count--; } } } if (draggedConnection != null) { Circuit receiver = draggedConnection.Receiver; if (receiver != null && draggedConnection.collidedCircuits.Count == 2) { Circuit sender = draggedConnection.Sender; Vector3 pos = draggedConnection.transform.position; SpawnGameConnection(sender, receiver, pos); } } if (draggedCutter != null) { List delete = new List(); foreach (Connection connection in Connections.FindAll(c => c.IsMine)) { if (connection.collidedConnections.Contains(draggedCutter)) delete.Add(connection); } foreach (Connection connection in delete) DestroyConnection(connection); } OnDragStopped(); } private void MoveDragged() { if (ScreenToPlaneRayCast(Input.mousePosition, out Vector3 point, true)) { if (draggedCircuit != null) { draggedCircuit.transform.position = point; bool isOut = !ScreenToPlaneRayCast(Input.mousePosition, out point); draggedCircuit.IsOutOfGamePlane = isOut; } if (draggedConnection != null) AdjustDraggedConnection(draggedConnection); if (draggedCutter != null) AdjustDraggedConnection(draggedCutter, true); } } private void AdjustDraggedConnection(Connection dragged, bool cutter = false) { Vector3 p1 = dragged.rootPosition; if (ScreenToPlaneRayCast(Input.mousePosition, out Vector3 p2)) { if (!cutter) { float range = dragged.Sender.Values.range; Circuit potencialReceiver = Circuits.Find( c => c.IsMouseOver && c.id != dragged.Sender.id && Vector3.Distance(p1, c.transform.position) <= range ); dragged.Receiver = potencialReceiver; if (potencialReceiver != null) p2 = potencialReceiver.transform.position; if (Vector3.Distance(p1, p2) > range) { Vector3 dir = (p2 - p1).normalized * range; p2 = p1 + dir; } } float scale = Vector3.Distance(p1, p2) * 4; Vector3 pos = (p1 + p2) / 2; Quaternion rot = Quaternion.FromToRotation(Vector3.up, p2 - p1); dragged.LengthScale = scale; dragged.transform.SetPositionAndRotation(pos, rot); } } //Gameplay public bool IsPlaying { get; private set; } private bool isOvertime; private float currentPlayTime; private float currentOvertime; public void StartGame() { GameGUI.Instance.StartGUI(); SpawnCircuit(); IsPlaying = true; Debug.Log("Game has started"); } private void UpdateGame() { if (PhotonNetwork.IsMasterClient) { foreach (Circuit circuit in Circuits) circuit.UpdateCircuit(tickRate); foreach (Connection connection in Connections) connection.UpdateConnection(tickRate); } foreach (Circuit circuit in circuitsToRemove) { Circuits.Remove(circuit); Destroy(circuit.gameObject); } circuitsToRemove.Clear(); foreach (Connection connection in connectionsToRemove) { Connections.Remove(connection); Destroy(connection.gameObject); } connectionsToRemove.Clear(); UpdateTime(); } private void UpdateTime() { float textTime; if (isOvertime) { currentOvertime += Time.deltaTime; if (currentOvertime >= overtime) { currentOvertime = overtime; if (PhotonNetwork.IsMasterClient) OnTimeRunnedOut(); } textTime = overtime - currentOvertime; } else { currentPlayTime += Time.deltaTime; if (currentPlayTime >= playTime * 60) { currentPlayTime = playTime * 60; if (PhotonNetwork.IsMasterClient) OnTimeRunnedOut(); } textTime = playTime * 60 - currentPlayTime; } GameGUI.Instance.SetTimeText(textTime); } private void OnTimeRunnedOut() { float[] charges = new float[5]; foreach (Circuit circuit in Circuits) { charges[circuit.ActorNumber] += circuit.Charge; } int greatestIndex = 0; for (int i = 0; i < charges.Length; i++) { float greatest = charges[greatestIndex]; if (charges[i] > greatest) greatestIndex = i; } bool isDraw(out List winners) { winners = new List(); for (int i = 0; i < charges.Length; i++) { if (charges[i] == charges[greatestIndex]) { winners.Add(i); } } if (winners.Count > 1) return true; return false; } if (isDraw(out List winnerActorNumbers)) { //Draw pv.RPC("RpcStartNewOvertime", RpcTarget.All, winnerActorNumbers.ToArray()); } else { //Unique winner pv.RPC("RpcFinishGame", RpcTarget.All, greatestIndex); } } [PunRPC] private void RpcStartNewOvertime(int[] winnerActorNumbers) { if (isOvertime) currentOvertime = 0; isOvertime = true; Debug.Log("The players " + string.Join(",", winnerActorNumbers) + " triggered an overtime!"); } [PunRPC] private void RpcFinishGame(int winnerActorNumber) { OnDragStopped(); GameGUI.Instance.ShowWinnerBannerPanel(winnerActorNumber); GameGUI.Instance.CloseCircuitMenu(); IsPlaying = false; Debug.Log("Player " + winnerActorNumber + " won the game!"); } //Circuit Management public void SendCircuitCharge(int circuitId, float charge) { pv.RPC("RpcApplyCircuitCharge", RpcTarget.Others, circuitId, charge); } [PunRPC] private void RpcApplyCircuitCharge(int circuitId, float charge) { Circuit circuit = GetCircuitById(circuitId); if (circuit == null) { Debug.LogWarning("Circuit to be updated is null"); return; } circuit.Charge = charge; } public void SpawnDraggableCircuit(Vector3 screenPos, CircuitType type, UICircuitCount uICIrcuitCount) { if (ScreenToPlaneRayCast(screenPos, out Vector3 hitPoint, true)) { Circuit circuit = SpawnCircuit(hitPoint, type, 1); circuit.ActorNumber = PhotonNetwork.LocalPlayer.ActorNumber; circuit.isDragged = true; circuit.uICircuitCount = uICIrcuitCount; draggedCircuit = circuit; } } public bool SpawnGameCircuit(Vector3 pos, CircuitType type, int level = 1) { Vector3 screenPos = cam.WorldToScreenPoint(pos); if (ScreenToPlaneRayCast(screenPos, out Vector3 hitPoint)) { SendSpawnCircuit(hitPoint, type, level); return true; } return false; } private void SpawnCircuit() { int index = PhotonNetwork.PlayerList.ToList().IndexOf(PhotonNetwork.LocalPlayer); Vector3 pos = circuitSpawns[index].transform.position; CircuitType type = CircuitType.Basic; int level = 2; float charge = GameManager.Instance.restPoints; SendSpawnCircuit(pos, type, level, charge); } private void SendSpawnCircuit(Vector3 pos, CircuitType type, int level, float charge = 0) { int actorNumber = PhotonNetwork.LocalPlayer.ActorNumber; int id = CreateCircuitId(); pv.RPC("RpcSpawnCircuit", RpcTarget.All, pos, type, level, charge, actorNumber, id); } private Circuit SpawnCircuit(Vector3 pos, CircuitType type, int level) { Quaternion rot = Quaternion.Euler(0, 90, 0); Transform parent = GameObject.Find("Circuits").transform; Vector3 scale = Vector3.one * circuitScale; CircuitPrefabGroup prefabGroup = circuitPrefabs.Find(x => x.type.Equals(type)); GameObject prefab = prefabGroup.GetValuesByLevel(level); GameObject circuitObject = Instantiate(prefab, pos, rot, parent); circuitObject.transform.localScale = scale; Circuit circuit = circuitObject.GetComponent(); circuit.type = type; circuit.level = level; return circuit; } [PunRPC] private Circuit RpcSpawnCircuit(Vector3 pos, CircuitType type, int level, float charge, int actorNumber, int id) { Circuit circuit = SpawnCircuit(pos, type, level); circuit.id = id; circuit.ActorNumber = actorNumber; circuit.Charge = charge; Circuits.Add(circuit); return circuit; } public void DestroyCircuit(Circuit circuit) { pv.RPC("RpcDestroyCircuit", RpcTarget.AllViaServer, circuit.id); } [PunRPC] private void RpcDestroyCircuit(int circuitId) { Circuit circuit = GetCircuitById(circuitId); if (circuit == null) return; foreach (Connection connection in circuit.GoingOutConnections) { RpcDestroyConnection(connection.id); } List friendlyInConnections = circuit.GoingInConnections.FindAll(c => c.ActorNumber == circuit.ActorNumber); foreach (Connection connection in friendlyInConnections) { RpcDestroyConnection(connection.id); } circuitsToRemove.Add(circuit); } public void UpgradeCircuit(Circuit circuit) { int newId = CreateCircuitId(); pv.RPC("RpcUpgradeCircuit", RpcTarget.AllViaServer, circuit.id, newId); } [PunRPC] private void RpcUpgradeCircuit(int circuitId, int newId) { Circuit oldCircuit = GetCircuitById(circuitId); if (oldCircuit == null) return; //New values Vector3 pos = oldCircuit.transform.position; CircuitType type = oldCircuit.type; int level = oldCircuit.level + 1; float charge = oldCircuit.Charge * 0.8f; int actorNumber = oldCircuit.ActorNumber; Circuit newCircuit = RpcSpawnCircuit(pos, type, level, charge, actorNumber, newId); foreach (Connection connection in oldCircuit.GoingOutConnections) connection.Sender = newCircuit; foreach (Connection connection in oldCircuit.GoingInConnections) connection.Receiver = newCircuit; RpcDestroyCircuit(oldCircuit.id); if (oldCircuit.IsSelected) newCircuit.Select(); } public void SendUpgradeCharge(Circuit circuit) { pv.RPC("RpcUpgradeCharge", RpcTarget.Others, circuit.UpgradeCharge, circuit.Charge, circuit.id); } [PunRPC] private void RpcUpgradeCharge(float upgradeCharge, float charge, int circuitId) { Circuit circuit = GetCircuitById(circuitId); if (circuit == null) return; circuit.Charge = charge; circuit.UpgradeCharge = upgradeCharge; } //Connection Management private void SpawnDraggableCutter(Vector3 pos) { Quaternion rot = Quaternion.Euler(90, 0, 0); Connection cutter = SpawnConnection(null, null, pos, rot, 0); cutter.ActorNumber = PhotonNetwork.LocalPlayer.ActorNumber; draggedCutter = cutter; } public void SpawnDraggableConnection(Vector3 pos, Circuit sender) { Quaternion rot = Quaternion.Euler(90, 0, 0); Connection connection = SpawnConnection(sender, null, pos, rot, 0); draggedConnection = connection; } public void SpawnGameConnection(Circuit sender, Circuit receiver, Vector3 pos) { Quaternion rot = draggedConnection.transform.rotation; float lengthScale = draggedConnection.LengthScale; SendSpawnConnection(sender, receiver, pos, rot, lengthScale); } private Connection SpawnConnection(Circuit sender, Circuit receiver, Vector3 pos, Quaternion rot, float lengthScale) { Transform parent = GameObject.Find("Connections").transform; GameObject connectionObject = Instantiate(cablePrefab, pos, rot, parent); Connection connection = connectionObject.GetComponent(); connection.Sender = sender; connection.Receiver = receiver; connection.LengthScale = lengthScale; connection.rootPosition = pos; return connection; } private void SendSpawnConnection(Circuit sender, Circuit receiver, Vector3 pos, Quaternion rot, float lengthScale) { int senderId = sender.id; int receiverId = receiver.id; int id = CreateConnectionId(); pv.RPC("RpcSpawnConnection", RpcTarget.All, pos, rot, lengthScale, senderId, receiverId, id); } [PunRPC] private void RpcSpawnConnection(Vector3 pos, Quaternion rot, float lengthScale, int senderId, int receiverId, int id) { Circuit sender = GetCircuitById(senderId); Circuit receiver = GetCircuitById(receiverId); Connection connection = SpawnConnection(sender, receiver, pos, rot, lengthScale); connection.id = id; Connections.Add(connection); GameGUI.Instance.UpdateMenuStats(); } public void DestroyConnection(Connection connection) { pv.RPC("RpcDestroyConnection", RpcTarget.AllViaServer, connection.id); } [PunRPC] private void RpcDestroyConnection(int connectionId) { Connection connection = GetConnectionById(connectionId); if (connection == null) return; connectionsToRemove.Add(connection); } //Charge Management public void SpawnConnectionCharge(Connection connection) { int actorNumber = connection.ActorNumber; int connectionId = connection.id; int id = connection.CreateChargeId(); pv.RPC("RpcSpawnCharge", RpcTarget.All, connectionId, id, actorNumber); } [PunRPC] private Charge RpcSpawnCharge(int connectionId, int id, int actorNumber) { Connection connection = GetConnectionById(connectionId); if (connection == null) return null; Charge charge = SpawnCharge(connection); charge.id = id; charge.ActorNumber = actorNumber; connection.AddCharge(charge); return charge; } private Charge SpawnCharge(Connection connection) { Transform parent = connection.transform; Vector3 pos = connection.Sender.transform.position; Quaternion rot = parent.rotation; Vector3 scale = new Vector3(1, 1 / parent.localScale.y * parent.localScale.x, 1); GameObject chargeObject = Instantiate(chargePrefab, pos, rot, parent); chargeObject.transform.localScale = scale; Charge charge = chargeObject.GetComponent(); charge.Speed = connection.Sender.Values.sendSpeed; return charge; } public void DestroyCharge(Connection connection, Charge charge) { int connectionId = connection.id; int id = charge.id; pv.RPC("RpcDestroyCharge", RpcTarget.All, connectionId, id); } [PunRPC] private void RpcDestroyCharge(int connectionId, int id) { Connection connection = GetConnectionById(connectionId); if (connection == null) return; Charge charge = connection.GetChargeById(id); connection.RemoveCharge(charge); Destroy(charge.gameObject); } //Useful, logical functions private bool ScreenToPlaneRayCast(Vector3 screenPos, out Vector3 hitPoint, bool outerPlane = false) { Ray ray = cam.ScreenPointToRay(screenPos); int layerMask; if (outerPlane) layerMask = LayerMask.GetMask("Outer Plane"); else layerMask = LayerMask.GetMask("Game Plane"); if (Physics.Raycast(ray, out RaycastHit info, Mathf.Infinity, layerMask)) { if (info.collider != null) { hitPoint = info.point; return true; } } hitPoint = Vector3.zero; return false; } private int CreateCircuitId() { int id; do id = Random.Range(0, 10000); while (Circuits.Exists(c => c.id == id)); return id; } private int CreateConnectionId() { int id; do id = Random.Range(0, 10000); while (Connections.Exists(c => c.id == id)); return id; } private Connection GetConnectionById(int id) { Connection connection = Connections.Find(c => c.id == id); return connection; } private Circuit GetCircuitById(int id) { Circuit circuit = Circuits.Find(c => c.id == id); return circuit; } public List CircuitsWithout(Circuit without) { List copy = new List(Circuits); copy.Remove(without); return copy; } }