Added a small grace period to survival mechanic to allow easier start of the game.

This commit is contained in:
2026-05-09 22:55:05 +02:00
parent 09df4334b9
commit 112728b5a9
4 changed files with 57 additions and 4 deletions
+1
View File
@@ -23,6 +23,7 @@ public class SurvivalSaveData
public bool IsDead { get; set; } public bool IsDead { get; set; }
public string DeathReason { get; set; } public string DeathReason { get; set; }
public string CurrentStatus { get; set; } public string CurrentStatus { get; set; }
public double ElapsedSeconds { get; set; }
} }
public class ItemSaveData public class ItemSaveData
+3 -1
View File
@@ -95,7 +95,8 @@ public static class SaveGameManager
Energy = GameData.survival.energy, Energy = GameData.survival.energy,
IsDead = GameData.survival.isDead, IsDead = GameData.survival.isDead,
DeathReason = GameData.survival.deathReason, DeathReason = GameData.survival.deathReason,
CurrentStatus = GameData.survival.currentStatus CurrentStatus = GameData.survival.currentStatus,
ElapsedSeconds = GameData.survival.elapsedSeconds
}; };
} }
@@ -282,6 +283,7 @@ public static class SaveGameManager
GameData.survival.isDead = survival.IsDead; GameData.survival.isDead = survival.IsDead;
GameData.survival.deathReason = survival.DeathReason ?? ""; GameData.survival.deathReason = survival.DeathReason ?? "";
GameData.survival.currentStatus = survival.CurrentStatus ?? ""; GameData.survival.currentStatus = survival.CurrentStatus ?? "";
GameData.survival.elapsedSeconds = survival.ElapsedSeconds;
} }
private static void ApplyInventoryData(List<ItemSaveData> savedItems) private static void ApplyInventoryData(List<ItemSaveData> savedItems)
+22 -3
View File
@@ -6,6 +6,7 @@ public class SurvivalState
private const float ThirstDrainPerSecond = 0.018f; private const float ThirstDrainPerSecond = 0.018f;
private const float PassiveEnergyDrainPerSecond = 0.01f; private const float PassiveEnergyDrainPerSecond = 0.01f;
private const float AutoConsumeThreshold = 30f; private const float AutoConsumeThreshold = 30f;
private const double GracePeriodSeconds = 900.0;
public float hunger = 100f; public float hunger = 100f;
public float thirst = 100f; public float thirst = 100f;
@@ -18,14 +19,19 @@ public class SurvivalState
public bool isDead = false; public bool isDead = false;
public string deathReason = ""; public string deathReason = "";
public string currentStatus = ""; public string currentStatus = "";
public double elapsedSeconds = 0;
public bool gracePeriodEnabled = true;
public void Update(double delta) public void Update(double delta)
{ {
if (isDead) return; if (isDead) return;
hunger = Math.Clamp(hunger - HungerDrainPerSecond * (float)delta, 0f, maxHunger); elapsedSeconds += delta;
thirst = Math.Clamp(thirst - ThirstDrainPerSecond * (float)delta, 0f, maxThirst); float drainModifier = IsInGracePeriod() ? 0.35f : 1f;
energy = Math.Clamp(energy - PassiveEnergyDrainPerSecond * (float)delta, 0f, maxEnergy);
hunger = Math.Clamp(hunger - HungerDrainPerSecond * drainModifier * (float)delta, 0f, maxHunger);
thirst = Math.Clamp(thirst - ThirstDrainPerSecond * drainModifier * (float)delta, 0f, maxThirst);
energy = Math.Clamp(energy - PassiveEnergyDrainPerSecond * drainModifier * (float)delta, 0f, maxEnergy);
TryAutoConsumeFood(); TryAutoConsumeFood();
TryAutoConsumeWater(); TryAutoConsumeWater();
@@ -106,6 +112,14 @@ public class SurvivalState
private void CheckDeath() private void CheckDeath()
{ {
if (IsInGracePeriod())
{
hunger = Math.Max(hunger, 1f);
thirst = Math.Max(thirst, 1f);
energy = Math.Max(energy, 1f);
return;
}
if (hunger <= 0f) if (hunger <= 0f)
{ {
KillPlayer("Starved"); KillPlayer("Starved");
@@ -131,4 +145,9 @@ public class SurvivalState
currentStatus = "Survival failed: " + reason; currentStatus = "Survival failed: " + reason;
GameData.canMove = false; GameData.canMove = false;
} }
private bool IsInGracePeriod()
{
return gracePeriodEnabled && elapsedSeconds < GracePeriodSeconds;
}
} }
+31
View File
@@ -12,6 +12,7 @@ public partial class TestRunner : Node
Run("Inventory adds, stacks and removes items", TestInventoryStacksAndRemovesItems); Run("Inventory adds, stacks and removes items", TestInventoryStacksAndRemovesItems);
Run("Inventory crafting checks stacked totals", TestInventoryCanCraftAcrossStacks); Run("Inventory crafting checks stacked totals", TestInventoryCanCraftAcrossStacks);
Run("Survival consumes stored food and water", TestSurvivalConsumesFoodAndWater); Run("Survival consumes stored food and water", TestSurvivalConsumesFoodAndWater);
Run("Survival grace period prevents early death", TestSurvivalGracePeriodPreventsEarlyDeath);
Run("Survival death disables movement", TestSurvivalDeathDisablesMovement); Run("Survival death disables movement", TestSurvivalDeathDisablesMovement);
Run("Robot research effects change robot stats", TestRobotResearchEffects); Run("Robot research effects change robot stats", TestRobotResearchEffects);
Run("Research completion applies effects once", TestResearchCompletionAppliesEffectsOnce); Run("Research completion applies effects once", TestResearchCompletionAppliesEffectsOnce);
@@ -21,6 +22,7 @@ public partial class TestRunner : Node
Run("Resource extraction and save data roundtrip", TestResourceSaveRoundtrip); Run("Resource extraction and save data roundtrip", TestResourceSaveRoundtrip);
Run("Robot save data roundtrip keeps robot state", TestRobotSaveRoundtrip); Run("Robot save data roundtrip keeps robot state", TestRobotSaveRoundtrip);
Run("Save data captures and restores global state", TestSaveDataRestoresGlobalState); 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); Run("Split save files store and load data", TestSplitSaveFilesRoundtrip);
Run("Split save files include one file per saved layer", TestSplitSaveFilesIncludeLayerFiles); Run("Split save files include one file per saved layer", TestSplitSaveFilesIncludeLayerFiles);
Run("If node evaluates inventory comparisons", TestIfNodeEvaluatesInventoryComparisons); Run("If node evaluates inventory comparisons", TestIfNodeEvaluatesInventoryComparisons);
@@ -95,6 +97,7 @@ public partial class TestRunner : Node
private void TestSurvivalDeathDisablesMovement() private void TestSurvivalDeathDisablesMovement()
{ {
GameData.canMove = true; GameData.canMove = true;
GameData.survival.gracePeriodEnabled = false;
GameData.survival.energy = 0.01f; GameData.survival.energy = 0.01f;
GameData.survival.Update(2.0); GameData.survival.Update(2.0);
@@ -103,6 +106,22 @@ public partial class TestRunner : Node
AssertFalse(GameData.canMove, "movement should be disabled"); AssertFalse(GameData.canMove, "movement should be disabled");
} }
private void TestSurvivalGracePeriodPreventsEarlyDeath()
{
GameData.canMove = true;
GameData.survival.hunger = 0f;
GameData.survival.thirst = 0f;
GameData.survival.energy = 0f;
GameData.survival.Update(1.0);
AssertFalse(GameData.survival.isDead, "survival should not fail during grace period");
AssertTrue(GameData.canMove, "movement should stay enabled during grace period");
AssertTrue(GameData.survival.hunger > 0f, "hunger should be clamped above zero");
AssertTrue(GameData.survival.thirst > 0f, "thirst should be clamped above zero");
AssertTrue(GameData.survival.energy > 0f, "energy should be clamped above zero");
}
private void TestRobotResearchEffects() private void TestRobotResearchEffects()
{ {
RobotStats stats = new RobotStats(); RobotStats stats = new RobotStats();
@@ -202,6 +221,18 @@ public partial class TestRunner : Node
AssertEqual(ResearchState.RESEARCHED, GameData.availableResearch["stoneage"].state, "saved research"); AssertEqual(ResearchState.RESEARCHED, GameData.availableResearch["stoneage"].state, "saved research");
} }
private void TestSaveDataRestoresSurvivalTimer()
{
GameData.survival.elapsedSeconds = 321.5;
SaveGameData saveData = SaveGameManager.CreateSaveData();
GameData.ResetRunState();
SaveGameManager.ApplyWorldData(saveData);
AssertClose(321.5f, (float)GameData.survival.elapsedSeconds, 0.001f, "saved survival timer");
}
private void TestResearchExecutionPaysResourcesAndFinishes() private void TestResearchExecutionPaysResourcesAndFinishes()
{ {
GameData.inventory.AddItem(new Item { data = GameData.availableItems["stone"] }, 5); GameData.inventory.AddItem(new Item { data = GameData.availableItems["stone"] }, 5);