5Y5T3M/Assets/Scripts/GameManager.cs
2025-09-19 01:07:22 +02:00

746 lines
No EOL
27 KiB
C#

using System;
using System.Collections.Generic;
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 GameObject hoverObject;
public TMP_Text energyText;
public TMP_Text currentPlayerText;
public TMP_Text timerText;
public GameObject actionListItemPrefab;
public Transform actionListParent;
public Button finishTurnBtn;
[SerializeField]
public List<Player> 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 float roundTime = 90;
public static GameManager Instance { get; private set; }
public bool regenerateOnChange = false;
[SerializeField]
public List<Action> 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<LevelData> levels = new List<LevelData>();
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<ActionType, string> ActionTypeDescription = new Dictionary<ActionType, string>
{
{ ActionType.NONE, "You should really never see this O_o \n(-1 Turn)" },
{ ActionType.MOVE_HALF_UNITS, "Read that title again... \n(1 Turn)" },
{ ActionType.MOVE_ALL_UNITS, "Read that title again... \n(1 Turn)" },
{ ActionType.ATTACK_NODE_WITH_HALF, "Read that title again... \n(1 Turn)" },
{ ActionType.ATTACK_NODE_WITH_ALL, "Read that title again... \n(1 Turn)" },
{ ActionType.ATTACK_CON_WITH_HALF, "Attacks the enemy node using half of your Units to break the pending connection \n(1 Turn)" },
{ ActionType.ATTACK_CON_WITH_ALL, "Attacks the enemy node using all of your Units to break the pending connection \n(1 Turn)" },
{ ActionType.CONSTRUCT_CON, "Begins construction of a connection \n(2 Turns)" },
{ ActionType.DESTRUCT_CON, "Begins deconstruction of a connection \n(2 Turns)" },
{ ActionType.EXPLODE_CON, "Begins blazingly fast deconstruction of a connection \n(1 Turn)" }
};
public Dictionary<ActionType, string> ActionTypeText = new Dictionary<ActionType, string>
{
{ 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<ActionType, int> ActionTypeEnergyUsage = new Dictionary<ActionType, int>
{
{ 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<NodeData> nodes = new List<NodeData>();
public List<ConnectionData> connections = new List<ConnectionData>();
}
float timer = 0;
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()
{
hoverObject.SetActive(false);
Player player = players.Find(p => p.id == currentPlayer);
int totalEnergyCost = 0;
actions.ForEach(a => totalEnergyCost += ActionTypeEnergyUsage[a.intendedAction]);
if (player != null && Application.isPlaying)
{
timer -= Time.deltaTime;
if (timer < 0)
{
ExecuteTurn();
return;
}
timerText.text = "[" + (int)timer / 60 + ":" + ((int)timer % 60 < 10 ? "0" : "") + $"{((int)timer % 60)}" + "]";
energyText.text = string.Concat(Enumerable.Repeat("□", Math.Max(0, player.energy - totalEnergyCost))) + string.Concat(Enumerable.Repeat("-", Math.Min(player.energy, totalEnergyCost)));
currentPlayerText.text = "[ Player " + currentPlayer + " ]";
}
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));
hoverObject.GetComponentsInChildren<TMP_Text>()[0].text = ActionTypeText[possibleAction];
hoverObject.GetComponentsInChildren<TMP_Text>()[1].text = ActionTypeDescription[possibleAction];
hoverObject.GetComponentsInChildren<TMP_Text>()[2].text = string.Concat(Enumerable.Repeat("□", ActionTypeEnergyUsage[possibleAction]));
hoverObject.SetActive(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 (!hoverObject.activeSelf)
{
Connection hoveredCon = GetConnections().Find(c => c.hovered);
if (hoveredCon != null)
{
ActionType possibleAction = CalcActionBetweenNodes(currentPlayer, hoveredCon.nodeA.id, hoveredCon.nodeB.id, Input.GetKey(altActionKey), true);
if (possibleAction != ActionType.NONE)
{
hoverObject.GetComponentsInChildren<TMP_Text>()[0].text = ActionTypeText[possibleAction];
hoverObject.GetComponentsInChildren<TMP_Text>()[1].text = ActionTypeDescription[possibleAction];
hoverObject.GetComponentsInChildren<TMP_Text>()[2].text = string.Concat(Enumerable.Repeat("□", ActionTypeEnergyUsage[possibleAction]));
hoverObject.SetActive(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<Node> GetNodes() => NodeParent.GetComponentsInChildren<Node>().ToList();
public List<Connection> GetConnections() => ConnectionParent != null ? ConnectionParent.GetComponentsInChildren<Connection>()?.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;
con.SetConnect();
}
else if (con.state == Connection.BuildState.DECONSTRUCTING)
{
con.state = Connection.BuildState.EMPTY;
con.DelConnect();
}
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));
for (int i = 0; i < actionListParent.childCount; i++)
{
Destroy(actionListParent.GetChild(i).gameObject);
}
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();
nextPlayer = players.Find(p => p.id == currentPlayer);
// Refill energy
nextPlayer.energy = 3;
nextPlayer.GetOwnedNodes().ForEach(node => {
nextPlayer.energy += node.Nachos;
if (node.Nachos > 0)
{
node.Units += 5 * node.Nachos;
}
});
timer = roundTime;
}
public void PushAction(Action action)
{
var ui = Instantiate(actionListItemPrefab, actionListParent);
ui.GetComponentInChildren<TMP_Text>().text = ActionTypeText[action.intendedAction] + " (" + action.nodeFromId + ">" + action.nodeToId + ")";
ui.GetComponentInChildren<Button>().onClick.AddListener(() => {
actions.Remove(action);
Destroy(ui);
});
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
};
int totalEnergyCost = 0;
actions.ForEach(a => totalEnergyCost += ActionTypeEnergyUsage[a.intendedAction]);
totalEnergyCost += ActionTypeEnergyUsage[action.intendedAction];
if (type != ActionType.NONE && players.Find(p => p.id == _player).energy >= totalEnergyCost )
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) PS : BONUS");
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);
int attackers = nodeFrom.Units / 2;
nodeTo.Units -= attackers;
nodeFrom.Units -= attackers;
if (nodeTo.Units <= 0)
{
nodeTo.Owner = currentPlayer;
nodeTo.Units = Math.Abs(nodeTo.Units);
}
if (nodeFrom.Units <= 0)
{
nodeFrom.Owner = -1;
nodeFrom.Units = 0;
}
break;
case ActionType.ATTACK_NODE_WITH_ALL:
Debug.Log("Attacking hostile units with all from " + nodeTo.id + " to " + nodeFrom.id);
attackers = nodeFrom.Units;
nodeTo.Units -= attackers;
nodeFrom.Units -= attackers;
if (nodeTo.Units <= 0)
{
nodeTo.Owner = nodeTo.Units == 0 ? -1 : currentPlayer;
nodeTo.Units = Math.Abs(nodeTo.Units);
}
if (nodeFrom.Units <= 0)
{
nodeFrom.Owner = -1;
nodeFrom.Units = 0;
}
break;
case ActionType.ATTACK_CON_WITH_HALF:
Debug.Log("Attacking hostile construction with half from " + nodeTo.id + " to " + nodeFrom.id);
attackers = nodeFrom.Units / 2;
nodeTo.Units -= attackers;
nodeFrom.Units = Math.Max(0, nodeFrom.Units -attackers);
if (nodeTo.Units <= 0)
{
nodeTo.Owner = nodeTo.Units == 0 ? -1 : currentPlayer;
con.state = Connection.BuildState.EMPTY;
con.DelConnect();
}
break;
case ActionType.ATTACK_CON_WITH_ALL:
Debug.Log("Attacking hostile construction with all from " + nodeTo.id + " to " + nodeFrom.id);
attackers = nodeFrom.Units;
nodeTo.Units -= attackers;
nodeFrom.Units = Math.Max(0, nodeFrom.Units - attackers);
if (nodeTo.Units <= 0)
{
nodeTo.Owner = nodeTo.Units == 0 ? -1 : currentPlayer;
con.state = Connection.BuildState.EMPTY;
con.DelConnect();
}
break;
case ActionType.CONSTRUCT_CON:
Debug.Log("Starting construction from " + nodeTo.id + " to " + nodeFrom.id);
con.state = Connection.BuildState.CONSTRUCTING;
con.SetConnect();
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.DelConnect();
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;
con.DelConnect();
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<Node>().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<Connection>();
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<LineRenderer>())
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>();
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;
}
}