refactor: enhance GameStateStore integration and improve skill management

This commit is contained in:
2026-03-19 02:33:07 +01:00
parent 3e36e48e97
commit eeefca4d4e
31 changed files with 260 additions and 419 deletions

View File

@@ -15,8 +15,11 @@ public partial class GhostEventHandler : Node
public override void _ExitTree()
{
EventBus.Instance.LevelStarted -= OnLevelStarted;
EventBus.Instance.LevelCompleted -= OnLevelCompleted;
if (EventBus.Instance != null)
{
EventBus.Instance.LevelStarted -= OnLevelStarted;
EventBus.Instance.LevelCompleted -= OnLevelCompleted;
}
}
private void OnLevelStarted(int levelIndex, Node currentScene)

View File

@@ -1,29 +0,0 @@
using Godot;
using Mr.BrickAdventures.Autoloads;
namespace Mr.BrickAdventures.scripts.Events;
/// <summary>
/// Handles level completion events and updates GameStateStore.
/// </summary>
public partial class LevelStateHandler : Node
{
public override void _Ready()
{
EventBus.Instance.LevelCompleted += OnLevelCompleted;
}
public override void _ExitTree()
{
if (EventBus.Instance != null)
{
EventBus.Instance.LevelCompleted -= OnLevelCompleted;
}
}
private void OnLevelCompleted(int levelIndex, Node currentScene, double completionTime)
{
// State mutations (commit coins/skills, reset session) are handled by GameManager.OnLevelComplete
// before this event fires. This handler is reserved for future level-specific side-effects.
}
}

View File

@@ -1 +0,0 @@
uid://gx5vn7viphv

View File

@@ -33,7 +33,6 @@ public partial class SkillCollectHandler : Node
GameStateStore.Instance?.UnlockSkillInSession(skill);
// Immediately activate the skill for the player
skill.Level = 1;
SkillManager?.AddSkill(skill);
// Emit skill unlocked event for UI/achievements

View File

@@ -13,7 +13,8 @@ public partial class SpeedRunEventHandler : Node
public override void _ExitTree()
{
EventBus.Instance.LevelCompleted -= OnLevelCompleted;
if (EventBus.Instance != null)
EventBus.Instance.LevelCompleted -= OnLevelCompleted;
}
private void OnLevelCompleted(int levelIndex, Node currentScene, double completionTime)

View File

@@ -1,19 +1,16 @@
using Godot;
using Mr.BrickAdventures.Autoloads;
using Mr.BrickAdventures.scripts.State;
namespace Mr.BrickAdventures.scripts.Events;
/// <summary>
/// Handles game events and updates statistics accordingly.
/// Listens to EventBus signals and increments relevant stats.
/// </summary>
public partial class StatisticsEventHandler : Node
{
private StatisticsManager StatisticsManager => StatisticsManager.Instance;
public override void _Ready()
{
// Subscribe to events
EventBus.Instance.CoinCollected += OnCoinCollected;
EventBus.Instance.EnemyDefeated += OnEnemyDefeated;
EventBus.Instance.PlayerDied += OnPlayerDied;
@@ -33,27 +30,17 @@ public partial class StatisticsEventHandler : Node
}
private void OnCoinCollected(int amount, Vector2 position)
{
StatisticsManager.IncrementStat("coins_collected", amount);
}
=> GameStateStore.Instance?.IncrementStat(StatNames.CoinsCollected, amount);
private void OnEnemyDefeated(Node enemy, Vector2 position)
{
StatisticsManager.IncrementStat("enemies_defeated");
}
=> GameStateStore.Instance?.IncrementStat(StatNames.EnemiesDefeated);
private void OnPlayerDied(Vector2 position)
{
StatisticsManager.IncrementStat("deaths");
}
=> GameStateStore.Instance?.IncrementStat(StatNames.Deaths);
private void OnLevelCompleted(int levelIndex, Node currentScene, double completionTime)
{
StatisticsManager.IncrementStat("levels_completed");
}
=> GameStateStore.Instance?.IncrementStat(StatNames.LevelsCompleted);
private void OnChildRescued(Vector2 position)
{
StatisticsManager.IncrementStat("children_rescued");
}
=> GameStateStore.Instance?.IncrementStat(StatNames.ChildrenRescued);
}

View File

@@ -0,0 +1,16 @@
using System.Collections.Generic;
namespace Mr.BrickAdventures.scripts.State;
public class GhostSaveData
{
public double BestTime { get; set; }
public List<GhostFrameDto> Frames { get; set; } = new();
}
public class GhostFrameDto
{
public double Timestamp { get; set; }
public float X { get; set; }
public float Y { get; set; }
}

View File

@@ -0,0 +1 @@
uid://drp68lkok8if3

View File

@@ -11,6 +11,11 @@ public class PlayerState
{
private const int DefaultLives = 3;
/// <summary>
/// The level index the player is currently on. Persisted across sessions.
/// </summary>
public int CurrentLevel { get; set; }
/// <summary>
/// Saved coins (not including current session).
/// </summary>
@@ -56,6 +61,7 @@ public class PlayerState
/// </summary>
public void Reset()
{
CurrentLevel = 0;
Coins = 0;
Lives = DefaultLives;
CompletedLevels.Clear();

View File

@@ -9,11 +9,6 @@ namespace Mr.BrickAdventures.scripts.State;
/// </summary>
public class SessionState
{
/// <summary>
/// Current level index being played.
/// </summary>
public int CurrentLevel { get; set; }
/// <summary>
/// Coins collected during this session (not yet saved).
/// </summary>
@@ -29,7 +24,6 @@ public class SessionState
/// </summary>
public static SessionState CreateDefault() => new()
{
CurrentLevel = 0,
CoinsCollected = 0,
SkillsUnlocked = new List<SkillData>()
};
@@ -44,11 +38,10 @@ public class SessionState
}
/// <summary>
/// Resets completely including level.
/// Resets all session state.
/// </summary>
public void ResetAll()
{
CurrentLevel = 0;
CoinsCollected = 0;
SkillsUnlocked.Clear();
}

View File

@@ -0,0 +1,10 @@
namespace Mr.BrickAdventures.scripts.State;
public static class StatNames
{
public const string CoinsCollected = "coins_collected";
public const string EnemiesDefeated = "enemies_defeated";
public const string Deaths = "deaths";
public const string LevelsCompleted = "levels_completed";
public const string ChildrenRescued = "children_rescued";
}

View File

@@ -0,0 +1 @@
uid://dtqjh4v5w41ni

View File

@@ -11,29 +11,26 @@ public partial class GameOverScreen : Control
[Export] public Button MainMenuButton { get; set; }
[Export] public PackedScene MainMenuScene { get; set; }
private GameManager _gameManager;
public override void _Ready()
{
_gameManager = GameManager.Instance;
RestartButton.Pressed += OnRestartClicked;
MainMenuButton.Pressed += OnMainMenuClicked;
}
private void OnMainMenuClicked()
{
_gameManager.ResetPlayerState();
GameStateStore.Instance?.ResetAll();
GetTree().ChangeSceneToPacked(MainMenuScene);
}
private void OnRestartClicked()
{
_gameManager.RestartGame();
GameManager.Instance?.RestartGame();
}
public void OnPlayerDeath()
{
if (_gameManager == null || _gameManager.GetLives() != 0) return;
if (GameStateStore.Instance?.Player.Lives != 0) return;
GameOverPanel.Show();
}

View File

@@ -12,12 +12,7 @@ public partial class Hud : Control
[Export] public ProgressBar HealthBar { get; set; }
[Export] public Label LivesLabel { get; set; }
private GameManager _gameManager;
public override void _Ready()
{
_gameManager = GameManager.Instance;
}
private GameStateStore Store => GameStateStore.Instance;
public override void _Process(double delta)
{
@@ -28,12 +23,12 @@ public partial class Hud : Control
private void SetCoinsLabel()
{
CoinsLabel.Text = Tr("COINS_LABEL") + ": " + _gameManager.GetCoins();
CoinsLabel.Text = Tr("COINS_LABEL") + ": " + (Store?.GetTotalCoins() ?? 0);
}
private void SetLivesLabel()
{
LivesLabel.Text = Tr("LIVES_LABEL") + ": " + _gameManager.GetLives();
LivesLabel.Text = Tr("LIVES_LABEL") + ": " + (Store?.Player.Lives ?? 0);
}
private void SetHealthBar()

View File

@@ -18,7 +18,7 @@ public partial class Marketplace : Control
[Export] public PackedScene MarketplaceButtonScene { get; set; }
[Export] public PackedScene SkillButtonScene { get; set; }
private GameManager GameManager => GameManager.Instance;
private GameStateStore Store => GameStateStore.Instance;
private SkillManager SkillManager => SkillManager.Instance;
private readonly List<Button> _unlockButtons = [];
private readonly List<SkillButton> _skillButtons = [];
@@ -33,7 +33,7 @@ public partial class Marketplace : Control
foreach (var skill in skillsToUnlock) CreateUpgradeButton(skill);
var unlockedSkills = GameManager.GetUnlockedSkills();
var unlockedSkills = Store.GetAllUnlockedSkills();
foreach (var skill in unlockedSkills) CreateSkillButton(skill);
SkillUnlockerComponent.SkillUnlocked += OnSkillUnlocked;
@@ -113,7 +113,7 @@ public partial class Marketplace : Control
private void OnUpgradeButtonPressed(SkillData skill)
{
if (GameManager.IsSkillUnlocked(skill))
if (Store.IsSkillUnlocked(skill))
{
if (skill.Level < skill.MaxLevel)
{

View File

@@ -13,14 +13,12 @@ public partial class MarketplaceButton : Button
[Export] public Texture2D LockedSkillIcon { get; set; }
[Export] public Container SkillLevelContainer { get; set; }
private GameManager _gameManager;
private SkillUnlockerComponent _skillUnlockerComponent;
private SkillManager _skillManager;
public override void _Ready()
{
_gameManager = GameManager.Instance;
var player = _gameManager.Player;
var player = GameManager.Instance?.Player;
if (player == null) return;
_skillUnlockerComponent = player.GetNodeOrNull<SkillUnlockerComponent>("SkillUnlockerComponent");
@@ -59,7 +57,7 @@ public partial class MarketplaceButton : Button
return;
}
var isUnlocked = _gameManager.IsSkillUnlocked(Data);
var isUnlocked = GameStateStore.Instance?.IsSkillUnlocked(Data) ?? false;
for (var i = 0; i < SkillLevelContainer.GetChildCount(); i++)
{

View File

@@ -53,7 +53,7 @@ public partial class PauseMenu : Control
private void OnMainMenuPressed()
{
GameManager.ResumeGame();
GameManager.ResetCurrentSessionState();
GameStateStore.Instance?.ResetSession();
GetTree().ChangeSceneToPacked(MainMenuScene);
}

View File

@@ -12,13 +12,11 @@ public partial class BrickShieldSkillComponent : SkillComponentBase
[Export] public PackedScene ShieldScene { get; set; }
private Node2D _shieldInstance;
private GameManager _gameManager;
private SkillManager _skillManager;
private HealthComponent _shieldHealth;
public override void _Ready()
{
_gameManager = GameManager.Instance;
_skillManager = SkillManager.Instance;
}
@@ -55,9 +53,9 @@ public partial class BrickShieldSkillComponent : SkillComponentBase
private void OnShieldDestroyed()
{
if (_gameManager != null && Data != null && _skillManager != null)
if (Data != null && _skillManager != null)
{
_gameManager.RemoveSkill(Data.Name);
GameStateStore.Instance?.RemoveUnlockedSkill(Data.Name);
_skillManager.RemoveSkill(Data.Name);
}
_shieldInstance = null;

View File

@@ -1,7 +1,6 @@
using Godot;
using Mr.BrickAdventures.Autoloads;
using Mr.BrickAdventures.scripts.interfaces;
using Mr.BrickAdventures.scripts.State;
namespace Mr.BrickAdventures.scripts.components;
@@ -28,8 +27,6 @@ public partial class ExitDoorComponent : Area2D, IUnlockable
EmitSignalExitTriggered();
AchievementManager.Instance?.UnlockAchievement(AchievementId);
var currentLevel = GameStateStore.Instance?.Session.CurrentLevel ?? 0;
GameManager.Instance?.UnlockLevel(currentLevel + 1);
CallDeferred(nameof(GoToNextLevel));
}

View File

@@ -1,10 +1,8 @@
using Godot;
using Godot.Collections;
using Mr.BrickAdventures;
using Mr.BrickAdventures.Autoloads;
using Mr.BrickAdventures.scripts.interfaces;
using Mr.BrickAdventures.scripts.Resources;
using Mr.BrickAdventures.scripts.State;
namespace Mr.BrickAdventures.scripts.components;
@@ -16,30 +14,24 @@ public partial class SkillUnlockerComponent : Node
[Signal]
public delegate void SkillUnlockedEventHandler(SkillData skill);
private GameManager _gameManager;
private GameStateStore Store => GameStateStore.Instance;
public override void _Ready()
{
_gameManager = GameManager.Instance;
SkillManager = SkillManager.Instance;
}
private bool HasEnoughCoins(int amount)
{
return _gameManager != null && _gameManager.GetCoins() >= amount;
}
private bool HasEnoughCoins(int amount) => (Store?.GetTotalCoins() ?? 0) >= amount;
public bool TryUnlockSkill(SkillData skill)
{
if (_gameManager == null) return false;
if (_gameManager.IsSkillUnlocked(skill)) return false;
if (Store == null) return false;
if (Store.IsSkillUnlocked(skill)) return false;
if (!HasEnoughCoins(skill.Upgrades[0].Cost)) return false;
skill.Level = 1;
_gameManager.RemoveCoins(skill.Upgrades[0].Cost);
// Add to session state via GameStateStore
GameStateStore.Instance?.UnlockSkillInSession(skill);
Store.RemoveCoins(skill.Upgrades[0].Cost);
Store.UnlockSkillInSession(skill);
SkillManager.AddSkill(skill);
EmitSignalSkillUnlocked(skill);
@@ -52,31 +44,29 @@ public partial class SkillUnlockerComponent : Node
foreach (var skill in availableSkills)
{
Store?.UnlockSkillPermanently(skill);
EmitSignalSkillUnlocked(skill);
}
_gameManager.UnlockSkills(availableSkills);
SkillManager.ApplyUnlockedSkills();
}
public bool TryUpgradeSkill(SkillData skill)
{
if (_gameManager == null) return false;
if (!_gameManager.IsSkillUnlocked(skill)) return false;
if (Store == null) return false;
if (!Store.IsSkillUnlocked(skill)) return false;
if (skill.Level >= skill.MaxLevel) return false;
if (!HasEnoughCoins(skill.Upgrades[skill.Level].Cost)) return false;
_gameManager.RemoveCoins(skill.Upgrades[skill.Level].Cost);
Store.RemoveCoins(skill.Upgrades[skill.Level].Cost);
skill.Level++;
if (SkillManager.ActiveComponents.TryGetValue(skill.Name, out Variant componentVariant))
{
var component = componentVariant.AsGodotObject();
if (component is ISkill skillInstance)
{
skillInstance.ApplyUpgrade(skill.Upgrades[skill.Level - 1]);
}
}
EmitSignalSkillUnlocked(skill);
return true;
}
}
}