From 09df4334b9a0d4e31e78f4dcccf0c1c94023574b Mon Sep 17 00:00:00 2001 From: Nicola Date: Sat, 9 May 2026 22:30:18 +0200 Subject: [PATCH] Implemented research cost, tweaked some values. --- Assets/Research.json | 16 +++--- Scripts/Gameplay/Crafting/Inventory.cs | 2 +- Scripts/Gameplay/Research/Research.cs | 20 +++++-- Scripts/Gameplay/Robots/Robot.cs | 10 ++-- Scripts/Gameplay/Survival/SurvivalState.cs | 18 +++--- Scripts/Tests/TestRunner.cs | 24 ++++++++ Scripts/UI/Research/ResearchList.cs | 64 ++++++++++++++++++---- Scripts/World/World.cs | 4 +- 8 files changed, 119 insertions(+), 39 deletions(-) diff --git a/Assets/Research.json b/Assets/Research.json index 6d0e418..6e415a5 100644 --- a/Assets/Research.json +++ b/Assets/Research.json @@ -20,7 +20,13 @@ ], "research": "basics", "crafttime": 4.0, - "texture": "res://Assets/Images/Research/StoneageSymbol.png" + "texture": "res://Assets/Images/Research/StoneageSymbol.png", + "effects": [ + { + "stat": "robot_count_increase", + "value": 10 + } + ] }, { "id": "stone_tools", @@ -36,13 +42,7 @@ ], "research": "stoneage", "crafttime": 5.0, - "texture": "res://Assets/Images/Items/StoneGearSymbol.png", - "effects": [ - { - "stat": "robot_count_increase", - "value": 10 - } - ] + "texture": "res://Assets/Images/Items/StoneGearSymbol.png" }, { "id": "basic_machines", diff --git a/Scripts/Gameplay/Crafting/Inventory.cs b/Scripts/Gameplay/Crafting/Inventory.cs index f9b7864..5d96218 100644 --- a/Scripts/Gameplay/Crafting/Inventory.cs +++ b/Scripts/Gameplay/Crafting/Inventory.cs @@ -5,7 +5,7 @@ public class Inventory { public List items = new List(); - public int maxInventorySize = 16; + public int maxInventorySize = 10; public event EventHandler OnInventoryUpdate; public bool AddItem(Item item, int amount) diff --git a/Scripts/Gameplay/Research/Research.cs b/Scripts/Gameplay/Research/Research.cs index 22c2888..1e3580c 100644 --- a/Scripts/Gameplay/Research/Research.cs +++ b/Scripts/Gameplay/Research/Research.cs @@ -15,10 +15,9 @@ public class Research { if (!paidResources) { - foreach (Ingredient ingredient in data.Inputs) - { - GameData.inventory.RemoveItem(ingredient.Item, ingredient.Amount); - } + if (!CanStart()) return ResearchResult.FAILED; + + PayResources(); paidResources = true; } @@ -31,6 +30,19 @@ public class Research return ResearchResult.RESEARCHING; } + public bool CanStart() + { + return GameData.inventory.CanCraft(data.Inputs, 1); + } + + public void PayResources() + { + foreach (Ingredient ingredient in data.Inputs) + { + GameData.inventory.RemoveItem(ingredient.Item, ingredient.Amount); + } + } + public void Complete() { if (state == ResearchState.RESEARCHED) return; diff --git a/Scripts/Gameplay/Robots/Robot.cs b/Scripts/Gameplay/Robots/Robot.cs index 280337d..7cf3f01 100644 --- a/Scripts/Gameplay/Robots/Robot.cs +++ b/Scripts/Gameplay/Robots/Robot.cs @@ -4,12 +4,12 @@ using Godot; public partial class Robot : Node3D { - private const float EnergyUsePerSecond = 0.2f; - private const float HeatGainPerSecond = 7.5f; - private const float ActiveHeatLossPerSecond = 18f; - private const float IdleHeatLossPerSecond = 9f; + private const float EnergyUsePerSecond = 0.12f; + private const float HeatGainPerSecond = 5f; + private const float ActiveHeatLossPerSecond = 22f; + private const float IdleHeatLossPerSecond = 12f; private const float CooldownTarget = 35f; - private const float MaintenanceLossPerSecond = 0.04f; + private const float MaintenanceLossPerSecond = 0.025f; private bool isExecuting = false; private ProgramNode currentNode; diff --git a/Scripts/Gameplay/Survival/SurvivalState.cs b/Scripts/Gameplay/Survival/SurvivalState.cs index 235d11f..ccb1a19 100644 --- a/Scripts/Gameplay/Survival/SurvivalState.cs +++ b/Scripts/Gameplay/Survival/SurvivalState.cs @@ -2,10 +2,10 @@ using System; public class SurvivalState { - private const float HungerDrainPerSecond = 0.035f; - private const float ThirstDrainPerSecond = 0.055f; - private const float PassiveEnergyDrainPerSecond = 0.025f; - private const float AutoConsumeThreshold = 35f; + private const float HungerDrainPerSecond = 0.012f; + private const float ThirstDrainPerSecond = 0.018f; + private const float PassiveEnergyDrainPerSecond = 0.01f; + private const float AutoConsumeThreshold = 30f; public float hunger = 100f; public float thirst = 100f; @@ -48,7 +48,7 @@ public class SurvivalState if (hunger > AutoConsumeThreshold) return; if (!GameData.inventory.TryRemoveItem("mushroom", 1)) return; - hunger = Math.Clamp(hunger + 35f, 0f, maxHunger); + hunger = Math.Clamp(hunger + 45f, 0f, maxHunger); } private void TryAutoConsumeWater() @@ -56,7 +56,7 @@ public class SurvivalState if (thirst > AutoConsumeThreshold) return; if (!GameData.inventory.TryRemoveItem("water", 1)) return; - thirst = Math.Clamp(thirst + 40f, 0f, maxThirst); + thirst = Math.Clamp(thirst + 50f, 0f, maxThirst); } private void TryAutoConsumeEnergy() @@ -65,19 +65,19 @@ public class SurvivalState if (GameData.inventory.TryRemoveItem("battery_v2", 1)) { - energy = Math.Clamp(energy + 70f, 0f, maxEnergy); + energy = Math.Clamp(energy + 90f, 0f, maxEnergy); return; } if (GameData.inventory.TryRemoveItem("battery_v1", 1)) { - energy = Math.Clamp(energy + 45f, 0f, maxEnergy); + energy = Math.Clamp(energy + 65f, 0f, maxEnergy); return; } if (GameData.inventory.TryRemoveItem("steam", 1)) { - energy = Math.Clamp(energy + 25f, 0f, maxEnergy); + energy = Math.Clamp(energy + 40f, 0f, maxEnergy); } } diff --git a/Scripts/Tests/TestRunner.cs b/Scripts/Tests/TestRunner.cs index 4b796e3..40ad6f1 100644 --- a/Scripts/Tests/TestRunner.cs +++ b/Scripts/Tests/TestRunner.cs @@ -16,6 +16,7 @@ public partial class TestRunner : Node Run("Robot research effects change robot stats", TestRobotResearchEffects); Run("Research completion applies effects once", TestResearchCompletionAppliesEffectsOnce); Run("Research execution pays resources and finishes", TestResearchExecutionPaysResourcesAndFinishes); + Run("Research cannot start without resources", TestResearchCannotStartWithoutResources); Run("Inventory add failure keeps inventory unchanged", TestInventoryAddFailureKeepsInventoryUnchanged); Run("Resource extraction and save data roundtrip", TestResourceSaveRoundtrip); Run("Robot save data roundtrip keeps robot state", TestRobotSaveRoundtrip); @@ -238,6 +239,29 @@ public partial class TestRunner : Node AssertEqual(0, GameData.inventory.items.Count, "failed add should not create stacks"); } + private void TestResearchCannotStartWithoutResources() + { + ResearchData researchData = new ResearchData + { + Id = "missing_stones", + Inputs = new List + { + new Ingredient { Item = "stone", Amount = 3 } + }, + Research = "basics", + CraftTime = 1.0, + Texture = "", + Effects = new List() + }; + + Research research = new Research(researchData); + ResearchResult result = research.Execute(1.0); + + AssertFalse(research.CanStart(), "research should not be startable"); + AssertEqual(ResearchResult.FAILED, result, "research should fail without resources"); + AssertEqual(ResearchState.UNDEFINED, research.state, "research state should stay unchanged"); + } + private void TestSplitSaveFilesRoundtrip() { GameData.inventory.AddItem(new Item { data = GameData.availableItems["water"] }, 7); diff --git a/Scripts/UI/Research/ResearchList.cs b/Scripts/UI/Research/ResearchList.cs index 2e3caaa..adf286d 100644 --- a/Scripts/UI/Research/ResearchList.cs +++ b/Scripts/UI/Research/ResearchList.cs @@ -23,12 +23,20 @@ public partial class ResearchList : PanelContainer if(currentResearch.Count > 0) toDelete = new List(); foreach (Research research in currentResearch) { - if (research.Execute(delta) == ResearchResult.FINISHED) + 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) { @@ -133,8 +141,8 @@ public partial class ResearchList : PanelContainer Button button = new Button { - Text = "Research", - Disabled = state != ResearchState.AVAILABLE, + Text = GetResearchButtonText(GameData.availableResearch[id], state), + Disabled = state != ResearchState.AVAILABLE || !GameData.availableResearch[id].CanStart(), TooltipText = tooltipText }; @@ -171,21 +179,57 @@ public partial class ResearchList : PanelContainer private void OnResearchPressed(string id) { - GameData.availableResearch[id].state = ResearchState.RESEARCHING; - currentResearch.Add(GameData.availableResearch[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) { - if (research.data.Effects == null || research.data.Effects.Count <= 0) - { - return Research.GetReadableName(research.data.Id); - } - 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:"); diff --git a/Scripts/World/World.cs b/Scripts/World/World.cs index e100b71..a24ad4b 100644 --- a/Scripts/World/World.cs +++ b/Scripts/World/World.cs @@ -304,7 +304,7 @@ public partial class World : Node3D } while (addedNewItem); //Step 3: Choose gate items needed based on crafting time and layer it is for (Lower layers -> More advanced items -> More crafting time) double goalCraftTime = Mathf.Lerp(lowestCraftTime, highestCraftTime, Mathf.Clamp(layer.level/(float)ruinSize, 0, 1)); - int ingredientAmount = rand.Next(1, 1 + ruinSize / 2); + int ingredientAmount = Mathf.Clamp(1 + layer.level / 3, 1, 4); float craftTimeModifier = 0f; double craftTimeLower, craftTimeUpper; for (int i = 0; i < ingredientAmount; i++) @@ -331,7 +331,7 @@ public partial class World : Node3D layer.gateIngredients.Add(new Ingredient { Item = item.Id, - Amount = rand.Next(5 + layer.level, 20 + layer.level) + Amount = rand.Next(3 + layer.level * 2, 9 + layer.level * 4) }); craftTimeModifier = 0f; }