using System; using System.Collections.Generic; using System.Dynamic; using System.Linq; using TMPro; using Unity.VisualScripting; using UnityEditor; using UnityEngine; using UnityEngine.UI; [ExecuteAlways] public class GameManager : MonoBehaviour { public Transform ConnectionParent; public GameObject ConnectionPrefab; public Transform NodeParent; public GameObject NodePrefab; public TMP_Text hoverText; public TMP_Text energyText; public TMP_Text currentPlayerText; public GameObject actionListItemPrefab; public Transform actionListParent; public Button finishTurnBtn; [SerializeField] public List players; public int currentPlayer = -1; public Node pressedNode = null; public Connection pressedCon = null; public KeyCode altActionKey = KeyCode.LeftShift; public float zoomSpeed = 20f; public float minFOV = 20f; public float maxFOV = 80f; public static GameManager Instance { get; private set; } public bool regenerateOnChange = false; [SerializeField] public List actions; [HideInInspector] public float minConnectionLength = 6; [HideInInspector] public float maxConnectionLength = 6; [HideInInspector] public int nodeCount = 100; [HideInInspector] public int hoverRadiusCon = 50; [HideInInspector] public int hoverRadiusNode = 50; [HideInInspector] public int selectedLevel = -1; [SerializeField] [HideInInspector] public List levels = new List(); public enum ActionType { NONE, MOVE_HALF_UNITS, MOVE_ALL_UNITS, ATTACK_NODE_WITH_HALF, ATTACK_NODE_WITH_ALL, ATTACK_CON_WITH_HALF, ATTACK_CON_WITH_ALL, CONSTRUCT_CON, DESTRUCT_CON, EXPLODE_CON }; public Dictionary ActionTypeText = new Dictionary { { ActionType.NONE, "" }, { ActionType.MOVE_HALF_UNITS, "Move troops (1/2)" }, { ActionType.MOVE_ALL_UNITS, "Move troops" }, { ActionType.ATTACK_NODE_WITH_HALF, "Attack (1/2)" }, { ActionType.ATTACK_NODE_WITH_ALL, "Attack" }, { ActionType.ATTACK_CON_WITH_HALF, "Interrupt Construction (1/2)" }, { ActionType.ATTACK_CON_WITH_ALL, "Interrupt Construction" }, { ActionType.CONSTRUCT_CON, "Construct" }, { ActionType.DESTRUCT_CON, "Destruct" }, { ActionType.EXPLODE_CON, "Explode (immediately)" } }; public Dictionary ActionTypeEnergyUsage = new Dictionary { { ActionType.NONE, 0}, { ActionType.MOVE_HALF_UNITS, 1}, { ActionType.MOVE_ALL_UNITS, 1}, { ActionType.ATTACK_NODE_WITH_HALF, 1}, { ActionType.ATTACK_NODE_WITH_ALL, 1}, { ActionType.ATTACK_CON_WITH_HALF, 1}, { ActionType.ATTACK_CON_WITH_ALL, 1}, { ActionType.CONSTRUCT_CON, 1}, { ActionType.DESTRUCT_CON, 1}, { ActionType.EXPLODE_CON, 2 } }; [Serializable] public struct Action { public ActionType intendedAction; public bool onConnection; public bool altAction; public int nodeFromId; public int nodeToId; public int amount; public int player; } [Serializable] public class LevelData { [Serializable] public class NodeData { public Vector3 position; public int owner; public int units; } [Serializable] public class ConnectionData { public int nodeAIndex; public int nodeBIndex; public bool allowed = true; } public float minConnectionLength = 0; public float maxConnectionLength = 0; public List nodes = new List(); public List connections = new List(); } void Awake() { if (Instance != null && Instance != this) { if (Application.isPlaying) Destroy(gameObject); return; } Instance = this; if (Application.isPlaying) DontDestroyOnLoad(gameObject); GetConnections().ForEach(obj => obj.DelConnect()); GetConnections().ForEach(obj => obj.SetConnect()); } private void Start() { LoadLevelData(selectedLevel); } private void Update() { hoverText.enabled = false; Player player = players.Find(p => p.id == currentPlayer); int totalEnergyCost = 0; actions.ForEach(a => totalEnergyCost += ActionTypeEnergyUsage[a.intendedAction]); if (player != null && Application.isPlaying) { energyText.text = "Energy: [" + string.Concat(Enumerable.Repeat("□", player.energy - totalEnergyCost)) + string.Concat(Enumerable.Repeat("-", totalEnergyCost)) + "]"; currentPlayerText.text = "[ Player " + currentPlayer + " ]"; foreach (TMP_Text item in actionListParent.GetComponentsInChildren()) Destroy(item.gameObject); actions.ForEach(a => Instantiate(actionListItemPrefab, actionListParent).GetComponentInChildren().text = ActionTypeText[a.intendedAction] + " (" + a.nodeFromId + ">" + a.nodeToId + ")"); } if (Input.GetMouseButtonDown(0)) { pressedNode = GetNodes().Find(n => n.hovered && n.Owner == currentPlayer); } if (Input.GetMouseButton(0)) { if(pressedNode != null) { Node node = GetNodes().Find(n => n.hovered && n != pressedNode); if (node) { Connection con = GetConnections().Find(c => (c.nodeA == pressedNode && c.nodeB == node) || (c.nodeB == pressedNode && c.nodeA == node)) ?? null; ActionType possibleAction = CalcActionBetweenNodes(currentPlayer, pressedNode.id, node.id, Input.GetKey(altActionKey)); hoverText.text = ActionTypeText[possibleAction]; hoverText.enabled = true; if (con != null && con.allowed) con.hovered = true; } } } if (Input.GetMouseButtonUp(0)) { if (pressedNode != null) { Node toNode = GetNodes().Find(n => n.hovered && pressedNode != n); if (toNode) { Connection con = GetConnections().Find(c => (c.nodeA == pressedNode && c.nodeB == toNode) || (c.nodeB == pressedNode && c.nodeA == toNode)) ?? null; if (con != null && con.allowed) PushNewAction(pressedNode.id, toNode.id); } } } if (!hoverText.enabled) { Connection hoveredCon = GetConnections().Find(c => c.hovered); if (hoveredCon != null) { ActionType possibleAction = CalcActionBetweenNodes(currentPlayer, hoveredCon.nodeA.id, hoveredCon.nodeB.id, Input.GetKey(altActionKey), true); hoverText.text = ActionTypeText[possibleAction]; hoverText.enabled = true; } } } public ActionType CalcActionBetweenNodes(int player, int nodeFromId, int nodeToId, bool altAction = false, bool onConnection = false) { Node nodeTo = GetNodes().Find(n => n.id == nodeToId); Node nodeFrom = GetNodes().Find(n => n.id == nodeFromId); Connection con = GetConnections().Find(c => (c.nodeA == nodeTo && c.nodeB == nodeFrom) || (c.nodeA == nodeFrom && c.nodeB == nodeTo)); if (con == null || con.allowed == false) return ActionType.NONE; // Clicked on connection if (onConnection) { if (con.state == Connection.BuildState.BUILT && nodeFrom.Owner == player && nodeTo.Owner == player) { return altAction ? ActionType.EXPLODE_CON : ActionType.DESTRUCT_CON; } else if (con.state == Connection.BuildState.EMPTY && (nodeFrom.Owner == player || nodeTo.Owner == player)) { return ActionType.CONSTRUCT_CON; } } // Dragged owned nodeFrom to nodeTo else if (nodeFrom.Owner == player) { // To Owned if (nodeTo.Owner == player) { switch (con.state) { case Connection.BuildState.EMPTY: return ActionType.CONSTRUCT_CON; case Connection.BuildState.CONSTRUCTING: case Connection.BuildState.DECONSTRUCTING: return ActionType.NONE; case Connection.BuildState.BUILT: return altAction ? ActionType.MOVE_HALF_UNITS : ActionType.MOVE_ALL_UNITS; } } // To Hostile else if (nodeTo.Owner >= 0) { switch (con.state) { case Connection.BuildState.EMPTY: return ActionType.CONSTRUCT_CON; case Connection.BuildState.CONSTRUCTING: return altAction ? ActionType.ATTACK_CON_WITH_HALF : ActionType.ATTACK_CON_WITH_ALL; case Connection.BuildState.DECONSTRUCTING: return ActionType.NONE; case Connection.BuildState.BUILT: return altAction ? ActionType.ATTACK_NODE_WITH_HALF : ActionType.ATTACK_NODE_WITH_ALL; } } // To Unclaimed else { switch (con.state) { case Connection.BuildState.EMPTY: return ActionType.CONSTRUCT_CON; case Connection.BuildState.CONSTRUCTING: case Connection.BuildState.DECONSTRUCTING: return ActionType.NONE; case Connection.BuildState.BUILT: return altAction ? ActionType.MOVE_HALF_UNITS : ActionType.MOVE_ALL_UNITS; } } } return ActionType.NONE; } public List GetNodes() => NodeParent.GetComponentsInChildren().ToList(); public List GetConnections() => ConnectionParent != null ? ConnectionParent.GetComponentsInChildren()?.ToList() ?? new() : new(); public void UpdateConstructions() { foreach(Connection con in GetConnections()) { if (con.constructingPlayerId == currentPlayer) { if (con.state == Connection.BuildState.CONSTRUCTING) con.state = Connection.BuildState.BUILT; else if (con.state == Connection.BuildState.DECONSTRUCTING) con.state = Connection.BuildState.EMPTY; con.constructingPlayerId = -1; } } } public void ExecuteTurn() { Player player = players.Find(p => p.id == currentPlayer); if (player == null) { Debug.LogWarning("Player " + currentPlayer + " not found!"); return; } int totalEnergyCost = 0; actions.ForEach(a => totalEnergyCost += ActionTypeEnergyUsage[a.intendedAction]); if (totalEnergyCost > player.energy) { Debug.Log("Insuficcient Energy for Turn! (needs " + totalEnergyCost + " has " + player.energy + ")"); return; } actions.ForEach(a => ExecuteAction(a)); actions.Clear(); // Select next player currentPlayer++; Player nextPlayer = players.Find(p => p.id == currentPlayer); // Executed turn of last Player => Round ended if(nextPlayer == null) currentPlayer = 0; UpdateConstructions(); // Refill energy players.Find(p => p.id == currentPlayer).energy = 3; } public void PushAction(Action action) => actions.Add(action); public Action PushNewAction(int nodeFromId, int nodeToId, bool onConnection = false, int? player = null, bool ? altAction = null) { int _player = player != null ? (int)player : currentPlayer; bool _altAction = altAction != null ? (bool)altAction : Input.GetKey(altActionKey); ActionType type = CalcActionBetweenNodes(_player, nodeFromId, nodeToId, _altAction, onConnection); Action action = new Action { intendedAction = type, nodeFromId = nodeFromId, nodeToId = nodeToId, player = _player, altAction = _altAction, onConnection = onConnection }; if(type != ActionType.NONE) PushAction(action); return action; } public void ExecuteAction(Action action) { Node nodeTo = GetNodes().Find(n => n.id == action.nodeToId); Node nodeFrom = GetNodes().Find(n => n.id == action.nodeFromId); Connection con = GetConnections().Find(c => (c.nodeA == nodeTo && c.nodeB == nodeFrom) || (c.nodeA == nodeFrom && c.nodeB == nodeTo)); Player p = players.Find(p => (p.id == action.player)); if (p == null) { Debug.LogWarning("Player " + action.player + " not found!"); return; } ActionType possibleAction = CalcActionBetweenNodes(action.player, action.nodeFromId, action.nodeToId, action.altAction, action.onConnection); if (possibleAction != action.intendedAction) { Debug.LogWarning("Intended action not possible (TODO: execute counter action)"); return; } if (p.energy < ActionTypeEnergyUsage[action.intendedAction]) { Debug.Log("Insuficcient Energy! (needs " + ActionTypeEnergyUsage[action.intendedAction] + " has " + p.energy + ")"); return; } switch (action.intendedAction) { case ActionType.NONE: break; case ActionType.MOVE_HALF_UNITS: case ActionType.MOVE_ALL_UNITS: Debug.Log("Moving units from " + nodeTo.id + " to " + nodeFrom.id); if (action.intendedAction == ActionType.MOVE_ALL_UNITS) { nodeTo.Units += nodeFrom.Units; nodeFrom.Units = 0; } else if (action.intendedAction == ActionType.MOVE_HALF_UNITS) { int diff = Mathf.CeilToInt(nodeFrom.Units / 2); nodeTo.Units += diff; nodeFrom.Units -= diff; } if (nodeFrom.Units <= 0) nodeFrom.Owner = -1; if (nodeTo.Units > 0) nodeTo.Owner = currentPlayer; break; case ActionType.ATTACK_NODE_WITH_HALF: Debug.Log("Attacking hostile units with half from " + nodeTo.id + " to " + nodeFrom.id); break; case ActionType.ATTACK_NODE_WITH_ALL: Debug.Log("Attacking hostile units with all from " + nodeTo.id + " to " + nodeFrom.id); break; case ActionType.ATTACK_CON_WITH_HALF: Debug.Log("Attacking hostile construction with half from " + nodeTo.id + " to " + nodeFrom.id); break; case ActionType.ATTACK_CON_WITH_ALL: Debug.Log("Attacking hostile construction with all from " + nodeTo.id + " to " + nodeFrom.id); break; case ActionType.CONSTRUCT_CON: Debug.Log("Starting construction from " + nodeTo.id + " to " + nodeFrom.id); con.state = Connection.BuildState.CONSTRUCTING; con.constructingPlayerId = action.player; break; case ActionType.DESTRUCT_CON: Debug.Log("Starten deconstruction from " + nodeTo.id + " to " + nodeFrom.id); con.state = Connection.BuildState.DECONSTRUCTING; con.constructingPlayerId = action.player; break; case ActionType.EXPLODE_CON: if (p.energy < 2) { Debug.Log("Not enough energy"); return; } p.energy -= 2; Debug.Log("Exploding connection from " + nodeTo.id + " to " + nodeFrom.id); con.state = Connection.BuildState.EMPTY; break; } } public void GenerateAlongSphere() { for (int i = NodeParent.childCount - 1; i >= 0; i--) DestroyImmediate(NodeParent.GetChild(i).gameObject); float radius = 20f; float goldenRatio = (1f + Mathf.Sqrt(5f)) / 2f; float angleIncrement = 2f * Mathf.PI * goldenRatio; for (int i = 0; i < nodeCount; i++) { float t = (float)i / nodeCount; // von 0 bis 1 float inclination = Mathf.Acos(1f - 2f * t); float azimuth = angleIncrement * i; float x = Mathf.Sin(inclination) * Mathf.Cos(azimuth); float y = Mathf.Sin(inclination) * Mathf.Sin(azimuth); float z = Mathf.Cos(inclination); Vector3 pos = new Vector3(x, y, z) * radius; var auto = PrefabUtility.InstantiatePrefab(NodePrefab, NodeParent) as GameObject; auto.GetComponent().id = i; auto.transform.localPosition = pos; } } public void GenerateConnections() { for (int i = ConnectionParent.childCount - 1; i >= 0; i--) DestroyImmediate(ConnectionParent.GetChild(i).gameObject); var nodes = GetNodes(); foreach (Node nodeA in nodes) { if (nodeA == null) continue; foreach (Node nodeB in nodes) { if (nodeB == null) continue; bool conExists = false; float dist = Vector3.Distance(nodeA.transform.position, nodeB.transform.position); if (nodeA == nodeB || dist > maxConnectionLength) continue; foreach (Connection con in GetConnections()) { if ((con.nodeA == nodeA && con.nodeB == nodeB) || (con.nodeA == nodeB && con.nodeB == nodeA)) { conExists = true; break; } } if (!conExists) { AddConnection(nodeA, nodeB, dist < minConnectionLength); } } } } public void AddConnection(Node nodeA, Node nodeB, bool allowed = true) { var newCon = PrefabUtility.InstantiatePrefab(ConnectionPrefab, ConnectionParent).GetComponent(); newCon.nodeA = nodeA; newCon.nodeB = nodeB; newCon.allowed = allowed; } public void LoadLevelData(int index) { if(index >= levels.Count) { Debug.LogWarning("LevelIndex out of range"); return; } for (int i = NodeParent.childCount - 1; i >= 0; i--) DestroyImmediate(NodeParent.GetChild(i).gameObject); foreach (LineRenderer line in ConnectionParent.GetComponentsInChildren()) DestroyImmediate(line.gameObject); for(int i = 0; i < levels[index].nodes.Count; i++) { var nodeData = levels[index].nodes[i]; var auto = PrefabUtility.InstantiatePrefab(NodePrefab, NodeParent) as GameObject; auto.transform.localPosition = nodeData.position; Node node = auto.GetComponent(); node.Owner = nodeData.owner; node.Units = nodeData.units; node.id = i; } var currentNodes = GetNodes(); int idx = 0; foreach (Node node in currentNodes) node.id = idx++; currentNodes = GetNodes(); foreach (LevelData.ConnectionData conData in levels[index].connections) AddConnection(currentNodes[conData.nodeAIndex], currentNodes[conData.nodeBIndex], conData.allowed); selectedLevel = index; minConnectionLength = levels[index].minConnectionLength; maxConnectionLength = levels[index].maxConnectionLength; nodeCount = currentNodes.Count; } public void SaveLevelData(int index = -1) { LevelData data = new LevelData(); data.minConnectionLength = minConnectionLength; data.maxConnectionLength = maxConnectionLength; // Nodes speichern foreach (var node in GetNodes()) { data.nodes.Add(new LevelData.NodeData { position = node.transform.localPosition, owner = node.Owner, units = node.Units }); } // Connections speichern foreach (var con in GetConnections()) { if (con.nodeA.id >= 0 && con.nodeB.id >= 0) { data.connections.Add(new LevelData.ConnectionData { nodeAIndex = con.nodeA.id, nodeBIndex = con.nodeB.id, allowed = con.allowed }); } } if (index == -1 || index >= levels.Count) levels.Add(data); else levels[index] = data; int newIndex = index < 0 ? levels.Count - 1 : index; selectedLevel = newIndex; } }