using Godot; using Godot.Collections; public partial class ResearchList : PanelContainer { [Export] private GraphEdit researchGraph; private System.Collections.Generic.Dictionary nodePositions = new System.Collections.Generic.Dictionary(); private bool hasArrangedNodes = false; public override void _Ready() { RecalculateResearchStates(); if (Visible) SetupGraph(); } public void SetupGraph() { RememberNodePositions(); ClearGraph(); CreateResearchNodes(); CreateResearchConnections(); if (!hasArrangedNodes) { researchGraph.ArrangeNodes(); hasArrangedNodes = true; } } private void RememberNodePositions() { foreach (Node child in researchGraph.GetChildren()) { GraphNode node = child as GraphNode; if (node == null) continue; nodePositions[node.Name] = node.PositionOffset; } } private void ClearGraph() { foreach (Dictionary connection in researchGraph.GetConnectionList()) { researchGraph.DisconnectNode( connection["from_node"].AsStringName(), (int)connection["from_port"], connection["to_node"].AsStringName(), (int)connection["to_port"] ); } foreach (Node child in researchGraph.GetChildren()) { if (child is GraphNode) { researchGraph.RemoveChild(child); child.QueueFree(); } } } private void CreateResearchNodes() { foreach (Research research in GameData.availableResearch.Values) { GraphNode node = CreateResearchNode( research.data.Id, research.data.Texture, research.state ); researchGraph.AddChild(node); } } private void CreateResearchConnections() { foreach (Research research in GameData.availableResearch.Values) { string prerequisite = research.data.Research; string current = research.data.Id; if (string.IsNullOrEmpty(prerequisite)) continue; if (!researchGraph.HasNode(prerequisite) || !researchGraph.HasNode(current)) continue; researchGraph.ConnectNode( prerequisite, 0, current, 0 ); } } private GraphNode CreateResearchNode(string id, string texturePath, ResearchState state) { Texture2D texture = GD.Load(texturePath); Color stateColor = GetColorByState(state); TextureRect icon = new TextureRect { Texture = texture, StretchMode = TextureRect.StretchModeEnum.KeepAspectCentered, CustomMinimumSize = new Vector2(64, 64), SelfModulate = stateColor }; Button button = new Button { Text = "Research", Disabled = state != ResearchState.AVAILABLE }; button.Pressed += () => OnResearchPressed(id); GraphNode node = new GraphNode { Name = id, Title = Research.GetReadableName(id), SelfModulate = stateColor }; if (nodePositions.ContainsKey(id)) { node.PositionOffset = nodePositions[id]; } node.SetSlot( 0, true, 0, Colors.White, true, 0, Colors.White ); node.AddChild(icon); node.AddChild(button); return node; } private void OnResearchPressed(string id) { GameData.availableResearch[id].state = ResearchState.RESEARCHED; RecalculateResearchStates(); SetupGraph(); } private void RecalculateResearchStates() { bool changedState = true; while (changedState) { changedState = false; foreach (Research research in GameData.availableResearch.Values) { ResearchState newState = GetUpdatedResearchState(research); if (research.state == newState) continue; research.state = newState; changedState = true; } } } private ResearchState GetUpdatedResearchState(Research research) { if (research.state == ResearchState.RESEARCHED) { return ResearchState.RESEARCHED; } if (research.data.Research.Length <= 0) { return ResearchState.RESEARCHED; } if (GameData.availableResearch[research.data.Research].state == ResearchState.RESEARCHED) { return ResearchState.AVAILABLE; } return ResearchState.LOCKED; } private Color GetColorByState(ResearchState state) { return state switch { ResearchState.AVAILABLE => Colors.White, ResearchState.LOCKED => new Color(0.6f, 0.6f, 0.6f, 0.5f), ResearchState.RESEARCHED => new Color(0.7f, 1f, 0.7f, 1f), _ => Colors.Red }; } }