using Godot; using Godot.Collections; using System.Collections.Generic; using System.Text; public partial class ResearchList : PanelContainer { [Export] private GraphEdit researchGraph; private System.Collections.Generic.Dictionary nodePositions = new System.Collections.Generic.Dictionary(); private bool hasArrangedNodes = false; private List currentResearch = new List(); private List toDelete = new List(); public override void _Ready() { RecalculateResearchStates(); if (Visible) SetupGraph(); } public override void _Process(double delta) { if(currentResearch.Count > 0) toDelete = new List(); foreach (Research research in currentResearch) { ResearchResult result = research.Execute(delta); if (result == ResearchResult.FINISHED) { toDelete.Add(research); RecalculateResearchStates(); SetupGraph(); } else if (result == ResearchResult.FAILED) { research.state = ResearchState.AVAILABLE; toDelete.Add(research); RecalculateResearchStates(); SetupGraph(); } } foreach (Research delete in toDelete) { currentResearch.Remove(delete); } } 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); string tooltipText = GetResearchTooltip(GameData.availableResearch[id]); TextureRect icon = new TextureRect { Texture = texture, StretchMode = TextureRect.StretchModeEnum.KeepAspectCentered, CustomMinimumSize = new Vector2(64, 64), SelfModulate = stateColor }; Button button = new Button { Text = GetResearchButtonText(GameData.availableResearch[id], state), Disabled = state != ResearchState.AVAILABLE || !GameData.availableResearch[id].CanStart(), TooltipText = tooltipText }; button.Pressed += () => OnResearchPressed(id); GraphNode node = new GraphNode { Name = id, Title = Research.GetReadableName(id), SelfModulate = stateColor, TooltipText = tooltipText }; 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) { Research research = GameData.availableResearch[id]; if (!research.CanStart()) return; if (currentResearch.Contains(research)) return; research.state = ResearchState.RESEARCHING; currentResearch.Add(research); RecalculateResearchStates(); SetupGraph(); } private string GetResearchButtonText(Research research, ResearchState state) { if (state == ResearchState.RESEARCHED) return "Done"; if (state == ResearchState.RESEARCHING) return "Researching"; if (state == ResearchState.LOCKED) return "Locked"; if (!research.CanStart()) return "Missing items"; return "Research"; } private string GetResearchTooltip(Research research) { StringBuilder tooltip = new StringBuilder(Research.GetReadableName(research.data.Id)); tooltip.AppendLine(); tooltip.AppendLine(); tooltip.AppendLine("Costs:"); if (research.data.Inputs.Count <= 0) { tooltip.AppendLine("- None"); } foreach (Ingredient ingredient in research.data.Inputs) { tooltip.Append("- "); tooltip.Append(ItemData.GetReadableName(ingredient.Item)); tooltip.Append(": "); tooltip.Append(GameData.inventory.GetItemAmount(ingredient.Item)); tooltip.Append("/"); tooltip.AppendLine(ingredient.Amount.ToString()); } tooltip.AppendLine(); tooltip.Append("Time: "); tooltip.AppendLine(research.data.CraftTime.ToString("0")); if (research.data.Effects == null || research.data.Effects.Count <= 0) { return tooltip.ToString(); } tooltip.AppendLine(); tooltip.AppendLine("Effects:"); foreach (ResearchEffect effect in research.data.Effects) { tooltip.Append("- "); tooltip.AppendLine(effect.GetDisplayText()); } return tooltip.ToString(); } 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 (research.state == ResearchState.RESEARCHING) { return ResearchState.RESEARCHING; } 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), ResearchState.RESEARCHING => new Color(0.3f, 1f, 0.3f, 1f), _ => Colors.Red }; } }