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 string DeathReason { get; set; }
public string CurrentStatus { get; set; }
public double ElapsedSeconds { get; set; }
}
public class ItemSaveData
+3 -1
View File
@@ -95,7 +95,8 @@ public static class SaveGameManager
Energy = GameData.survival.energy,
IsDead = GameData.survival.isDead,
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.deathReason = survival.DeathReason ?? "";
GameData.survival.currentStatus = survival.CurrentStatus ?? "";
GameData.survival.elapsedSeconds = survival.ElapsedSeconds;
}
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 PassiveEnergyDrainPerSecond = 0.01f;
private const float AutoConsumeThreshold = 30f;
private const double GracePeriodSeconds = 900.0;
public float hunger = 100f;
public float thirst = 100f;
@@ -18,14 +19,19 @@ public class SurvivalState
public bool isDead = false;
public string deathReason = "";
public string currentStatus = "";
public double elapsedSeconds = 0;
public bool gracePeriodEnabled = true;
public void Update(double delta)
{
if (isDead) return;
hunger = Math.Clamp(hunger - HungerDrainPerSecond * (float)delta, 0f, maxHunger);
thirst = Math.Clamp(thirst - ThirstDrainPerSecond * (float)delta, 0f, maxThirst);
energy = Math.Clamp(energy - PassiveEnergyDrainPerSecond * (float)delta, 0f, maxEnergy);
elapsedSeconds += delta;
float drainModifier = IsInGracePeriod() ? 0.35f : 1f;
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();
TryAutoConsumeWater();
@@ -106,6 +112,14 @@ public class SurvivalState
private void CheckDeath()
{
if (IsInGracePeriod())
{
hunger = Math.Max(hunger, 1f);
thirst = Math.Max(thirst, 1f);
energy = Math.Max(energy, 1f);
return;
}
if (hunger <= 0f)
{
KillPlayer("Starved");
@@ -131,4 +145,9 @@ public class SurvivalState
currentStatus = "Survival failed: " + reason;
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 crafting checks stacked totals", TestInventoryCanCraftAcrossStacks);
Run("Survival consumes stored food and water", TestSurvivalConsumesFoodAndWater);
Run("Survival grace period prevents early death", TestSurvivalGracePeriodPreventsEarlyDeath);
Run("Survival death disables movement", TestSurvivalDeathDisablesMovement);
Run("Robot research effects change robot stats", TestRobotResearchEffects);
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("Robot save data roundtrip keeps robot state", TestRobotSaveRoundtrip);
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 include one file per saved layer", TestSplitSaveFilesIncludeLayerFiles);
Run("If node evaluates inventory comparisons", TestIfNodeEvaluatesInventoryComparisons);
@@ -95,6 +97,7 @@ public partial class TestRunner : Node
private void TestSurvivalDeathDisablesMovement()
{
GameData.canMove = true;
GameData.survival.gracePeriodEnabled = false;
GameData.survival.energy = 0.01f;
GameData.survival.Update(2.0);
@@ -103,6 +106,22 @@ public partial class TestRunner : Node
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()
{
RobotStats stats = new RobotStats();
@@ -202,6 +221,18 @@ public partial class TestRunner : Node
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()
{
GameData.inventory.AddItem(new Item { data = GameData.availableItems["stone"] }, 5);