Added final features for this release. Now only polishing (if needed) remains.

Features: Sacrifice-Node, Maintain-Node, Options for screen type, lightcolor and soundvolume, tied in sound effects, game pause when menu is open, visibly open up gate when opening it.
This commit is contained in:
2026-05-10 14:09:14 +02:00
parent 228e81ab4e
commit 8170b700b2
28 changed files with 797 additions and 14 deletions
+135
View File
@@ -19,8 +19,15 @@ public partial class TestRunner : Node
Run("Research execution pays resources and finishes", TestResearchExecutionPaysResourcesAndFinishes);
Run("Research cannot start without resources", TestResearchCannotStartWithoutResources);
Run("Inventory add failure keeps inventory unchanged", TestInventoryAddFailureKeepsInventoryUnchanged);
Run("Saved scripts can be deleted", TestSavedScriptsCanBeDeleted);
Run("Resource extraction and save data roundtrip", TestResourceSaveRoundtrip);
Run("Endless resources extract slower", TestEndlessResourcesExtractSlower);
Run("Robot save data roundtrip keeps robot state", TestRobotSaveRoundtrip);
Run("Maintain node consumes matching gear", TestMaintainNodeConsumesMatchingGear);
Run("Sacrifice node makes resource endless", TestSacrificeNodeMakesResourceEndless);
Run("No robot recovery detects loss", TestNoRobotRecoveryDetectsLoss);
Run("Robot item prevents no robot loss", TestRobotItemPreventsNoRobotLoss);
Run("Save data captures and restores options", TestSaveDataRestoresOptions);
Run("Save data captures and restores global state", TestSaveDataRestoresGlobalState);
Run("Save data restores survival timer", TestSaveDataRestoresSurvivalTimer);
Run("Split save files store and load data", TestSplitSaveFilesRoundtrip);
@@ -28,6 +35,8 @@ public partial class TestRunner : Node
Run("If node evaluates inventory comparisons", TestIfNodeEvaluatesInventoryComparisons);
Run("While node reports false conditions", TestWhileNodeReportsFalseConditions);
Run("For node stops after configured amount", TestForNodeStopsAfterConfiguredAmount);
Run("Paused world does not drain survival", TestPausedWorldDoesNotDrainSurvival);
Run("Open gate hides gate content", TestOpenGateHidesGateContent);
Run("Item data readable names are stable", TestItemDataReadableNames);
Run("Resource files load core game data", TestResourceFilesLoadCoreData);
@@ -173,6 +182,20 @@ public partial class TestRunner : Node
AssertEqual(saveData.CurrentAmount, loadedResource.CreateSaveData().CurrentAmount, "resource amount");
}
private void TestEndlessResourcesExtractSlower()
{
GameResource resource = new GameResource("stone");
AssertTrue(resource.Extract(1.0), "normal resource should extract after one second");
resource.MakeEndless();
AssertFalse(resource.Extract(1.0), "endless resource should not extract after one second");
AssertTrue(resource.Extract(3.0), "endless resource should extract after four total seconds");
AssertTrue(resource.IsEndless(), "resource should be endless");
AssertTrue(resource.GetExtractionSpeed() > 1f, "endless extraction speed should be slower");
}
private void TestRobotSaveRoundtrip()
{
Robot robot = new Robot
@@ -201,6 +224,59 @@ public partial class TestRunner : Node
AssertTrue(loadedRobot.isCoolingDown, "robot cooling state");
}
private void TestMaintainNodeConsumesMatchingGear()
{
Robot robot = new Robot
{
robotType = "copper_robot",
maintenance = 50f,
isBroken = true
};
GameData.inventory.AddItem(new Item { data = GameData.availableItems["copper_gear"] }, 1);
MaintainNode node = new MaintainNode();
AssertEqual(NodeResult.SUCCESS, node.Execute(robot, 0), "maintain should succeed");
AssertClose(60f, robot.maintenance, 0.001f, "maintenance repaired by 10");
AssertFalse(robot.isBroken, "robot should be usable again");
AssertEqual(0, GameData.inventory.GetItemAmount("copper_gear"), "gear should be consumed");
}
private void TestSacrificeNodeMakesResourceEndless()
{
GameData.ruinSize = 1;
GameData.layerSize = 1;
GameData.map = new Layer[1];
GameData.map[0] = CreateTestLayer(0, "spawn");
GameData.map[0].tiles[0, 0].resource = new GameResource("stone");
GameData.map[0].tiles[0, 0].containsResource = true;
Pathfinding.BuildAStarGraph();
Robot robot = new Robot
{
Position = Vector3.Zero
};
GameData.robots.Add(robot);
SacrificeNode node = new SacrificeNode();
AssertEqual(NodeResult.SUCCESS, node.Execute(robot, 0), "sacrifice should succeed");
AssertTrue(GameData.map[0].tiles[0, 0].resource.IsEndless(), "resource should become endless");
AssertEqual(0, GameData.robots.Count, "robot should be removed");
}
private void TestNoRobotRecoveryDetectsLoss()
{
AssertTrue(GameData.HasNoRobotRecovery(), "no robots and no robot items should be a loss");
}
private void TestRobotItemPreventsNoRobotLoss()
{
GameData.inventory.AddItem(new Item { data = GameData.availableItems["stone_robot"] }, 1);
AssertFalse(GameData.HasNoRobotRecovery(), "robot item should prevent loss");
}
private void TestSaveDataRestoresGlobalState()
{
GameData.inventory.AddItem(new Item { data = GameData.availableItems["stone"] }, 12);
@@ -221,6 +297,27 @@ public partial class TestRunner : Node
AssertEqual(ResearchState.RESEARCHED, GameData.availableResearch["stoneage"].state, "saved research");
}
private void TestSaveDataRestoresOptions()
{
GameData.screenMode = 1;
GameData.soundVolume = 0.35f;
GameData.lightColor = new Color(0.2f, 0.4f, 0.6f, 1f);
SaveGameData saveData = SaveGameManager.CreateSaveData();
GameData.screenMode = 2;
GameData.soundVolume = 0.8f;
GameData.lightColor = Colors.White;
SaveGameManager.ApplyWorldData(saveData);
AssertEqual(1, GameData.screenMode, "saved screen mode");
AssertClose(0.35f, GameData.soundVolume, 0.001f, "saved sound volume");
AssertClose(0.2f, GameData.lightColor.R, 0.001f, "saved light red");
AssertClose(0.4f, GameData.lightColor.G, 0.001f, "saved light green");
AssertClose(0.6f, GameData.lightColor.B, 0.001f, "saved light blue");
}
private void TestSaveDataRestoresSurvivalTimer()
{
GameData.survival.elapsedSeconds = 321.5;
@@ -270,6 +367,17 @@ public partial class TestRunner : Node
AssertEqual(0, GameData.inventory.items.Count, "failed add should not create stacks");
}
private void TestSavedScriptsCanBeDeleted()
{
string scriptName = "delete_test_script";
FileHandler.SaveProgram(scriptName, "Name: Explore;");
AssertTrue(FileHandler.LoadProgramNames().Contains(scriptName), "script should be listed");
AssertTrue(FileHandler.DeleteProgram(scriptName), "delete should succeed");
AssertEqual("", FileHandler.LoadProgram(scriptName), "deleted script should be empty");
AssertFalse(FileHandler.DeleteProgram(scriptName), "second delete should fail");
}
private void TestResearchCannotStartWithoutResources()
{
ResearchData researchData = new ResearchData
@@ -367,6 +475,33 @@ public partial class TestRunner : Node
AssertEqual(NodeResult.CONDITIONFALSE, node.Execute(null, 0), "loop finished");
}
private void TestPausedWorldDoesNotDrainSurvival()
{
GameData.isPaused = true;
GameData.canMove = false;
GameData.survival.energy = 100f;
World world = new World();
world.UpdateGameLoop(100.0);
AssertClose(100f, GameData.survival.energy, 0.001f, "energy should not drain while paused");
}
private void TestOpenGateHidesGateContent()
{
Layer layer = CreateTestLayer(0, "gate");
layer.gateCoordinate = new Vector2I(0, 0);
layer.tiles[0, 0].ContentNode = new Node3D
{
Visible = true
};
layer.OpenGate();
AssertTrue(layer.isGateOpen, "gate should be open");
AssertFalse(layer.tiles[0, 0].ContentNode.Visible, "gate content should be hidden");
}
private void TestItemDataReadableNames()
{
AssertEqual("Iron gear", ItemData.GetReadableName("iron_gear"), "readable name");