feat: lives and death move to the new domain (commit C of the swap)
fixes the regression commit B introduced: coins and skills lived in the Attempt ledger, but nothing discarded it, because LivesStateHandler still called the old store and HandlePlayerDeath was never invoked. Dying kept everything earned that level. Discard-on-death is the ledger's purpose, so it could not be separated from it. new AttemptFailed(remaining, runOver) event, raised after the life is spent and the ledger discarded. DeathScreen and GameOverScreen react to that instead of being wired to HealthComponent.Death by scene connections which fired *before* the life was charged - on the last life game-over saw one life left and showed nothing, then the death screen saw zero and refused to reload. 18 scene connections deleted; the race is now unrepresentable rather than fixed. DeathScreen also had two read paths for lives - a signal and a direct store read - because neither was reliable alone. Now one. Hud reads both facts from signals; only health still polls, and that belongs to the component rather than to progression. GameManager no longer touches GameStateStore or SaveSystem at all.
This commit is contained in:
@@ -161,6 +161,16 @@ public partial class EventBus : Node
|
||||
/// </summary>
|
||||
[Signal] public delegate void RunEndedEventHandler();
|
||||
|
||||
/// <summary>
|
||||
/// A death has been settled. The life is already spent and the attempt already discarded, so
|
||||
/// <paramref name="remainingLives"/> is safe to display directly — unlike reading Lives from a
|
||||
/// handler racing the one that writes it.
|
||||
/// </summary>
|
||||
[Signal] public delegate void AttemptFailedEventHandler(int remainingLives, bool runOver);
|
||||
|
||||
public static void EmitAttemptFailed(int remainingLives, bool runOver)
|
||||
=> Instance?.EmitSignal(SignalName.AttemptFailed, remainingLives, runOver);
|
||||
|
||||
public static void EmitRunEnded()
|
||||
=> Instance?.EmitSignal(SignalName.RunEnded);
|
||||
|
||||
|
||||
@@ -15,9 +15,9 @@ namespace Mr.BrickAdventures.Autoloads;
|
||||
/// of an inspector array, so reordering levels silently invalidated every save.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Transitional: coins, lives and skills are still owned by <c>GameStateStore</c> and persisted by
|
||||
/// <c>SaveSystem</c>. Only level progression has moved. The two are kept apart deliberately — nothing
|
||||
/// is tracked in both places at once.
|
||||
/// Progression is persisted by <see cref="ProgressionRoot"/>'s stores, which save themselves at the
|
||||
/// points that matter — a First Clear and a death — so there is no separate "save the game" step to
|
||||
/// forget.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public partial class GameManager : Node
|
||||
@@ -30,7 +30,6 @@ public partial class GameManager : Node
|
||||
|
||||
private PlayerController _player;
|
||||
private SpeedRunManager SpeedRunManager => SpeedRunManager.Instance;
|
||||
private GameStateStore Store => GameStateStore.Instance;
|
||||
private ProgressionRoot Progression => ProgressionRoot.Instance;
|
||||
|
||||
public static GameManager Instance { get; private set; }
|
||||
@@ -60,10 +59,8 @@ public partial class GameManager : Node
|
||||
{
|
||||
if (Progression == null) return;
|
||||
|
||||
Store?.ResetAll();
|
||||
Progression.StartPlaythrough(ProgressionRoot.SoleSlot);
|
||||
SpeedRunManager?.StartTimer();
|
||||
SaveSystem.Instance.SaveGame();
|
||||
EventBus.EmitGameStarted();
|
||||
|
||||
GoTo(Progression.Campaign.First);
|
||||
@@ -73,10 +70,6 @@ public partial class GameManager : Node
|
||||
{
|
||||
if (Progression == null) return;
|
||||
|
||||
// Coins and skills still come from the old save; level progression from the new one.
|
||||
if (!SaveSystem.Instance.LoadGame())
|
||||
GD.PrintErr("GameManager: no legacy save to continue; starting fresh.");
|
||||
|
||||
if (!Progression.LoadPlaythrough(ProgressionRoot.SoleSlot))
|
||||
{
|
||||
var why = Progression.PeekPlaythrough(ProgressionRoot.SoleSlot);
|
||||
@@ -101,10 +94,8 @@ public partial class GameManager : Node
|
||||
{
|
||||
if (Progression == null) return;
|
||||
|
||||
Store?.ResetAll();
|
||||
Progression.StartPlaythrough(ProgressionRoot.SoleSlot);
|
||||
SpeedRunManager?.StartTimer();
|
||||
SaveSystem.Instance.SaveGame();
|
||||
|
||||
GoTo(Progression.Campaign.First);
|
||||
}
|
||||
@@ -122,9 +113,6 @@ public partial class GameManager : Node
|
||||
// position if this was a First Clear.
|
||||
Progression.FinishLevel(time);
|
||||
|
||||
// Still needed for lives, which move with the death path in the next commit.
|
||||
SaveSystem.Instance.SaveGame();
|
||||
|
||||
if (Progression.CurrentLevel is { } next && !next.Equals(finished))
|
||||
GoTo(next);
|
||||
}
|
||||
|
||||
@@ -51,6 +51,10 @@ public sealed class ProgressionEventRepublisher
|
||||
EventBus.EmitRunEnded();
|
||||
break;
|
||||
|
||||
case AttemptFailed failed:
|
||||
EventBus.EmitAttemptFailed(failed.Remaining.Value, failed.RunOver);
|
||||
break;
|
||||
|
||||
case AchievementUnlocked unlocked:
|
||||
EventBus.EmitAchievementUnlocked(unlocked.Achievement.Value);
|
||||
break;
|
||||
|
||||
Reference in New Issue
Block a user