Added a small grace period to survival mechanic to allow easier start of the game.
This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
Reference in New Issue
Block a user