refactor: enhance GameStateStore integration and improve skill management
This commit is contained in:
@@ -77,6 +77,7 @@ public partial class AchievementManager : Node
|
|||||||
// 1. Mark as unlocked
|
// 1. Mark as unlocked
|
||||||
unlockedIds.Add(achievementId);
|
unlockedIds.Add(achievementId);
|
||||||
GD.Print($"Achievement Unlocked: {achievement.DisplayName}");
|
GD.Print($"Achievement Unlocked: {achievement.DisplayName}");
|
||||||
|
EventBus.EmitAchievementUnlocked(achievementId);
|
||||||
|
|
||||||
// 2. Show the UI popup
|
// 2. Show the UI popup
|
||||||
if (AchievementPopupScene != null)
|
if (AchievementPopupScene != null)
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
using Godot;
|
using Godot;
|
||||||
using Mr.BrickAdventures;
|
|
||||||
using Mr.BrickAdventures.scripts.components;
|
using Mr.BrickAdventures.scripts.components;
|
||||||
|
|
||||||
namespace Mr.BrickAdventures.Autoloads;
|
namespace Mr.BrickAdventures.Autoloads;
|
||||||
|
|
||||||
public partial class ConsoleManager : Node
|
public partial class ConsoleManager : Node
|
||||||
{
|
{
|
||||||
|
private GameStateStore Store => GameStateStore.Instance;
|
||||||
private GameManager GameManager => GameManager.Instance;
|
private GameManager GameManager => GameManager.Instance;
|
||||||
private AchievementManager AchievementManager => AchievementManager.Instance;
|
private AchievementManager AchievementManager => AchievementManager.Instance;
|
||||||
private SkillManager _skillManager;
|
private SkillManager _skillManager;
|
||||||
@@ -18,114 +18,103 @@ public partial class ConsoleManager : Node
|
|||||||
|
|
||||||
private void AddCoinsCommand(int amount)
|
private void AddCoinsCommand(int amount)
|
||||||
{
|
{
|
||||||
GameManager.AddCoins(amount);
|
if (Store == null) return;
|
||||||
|
Store.Player.Coins += amount;
|
||||||
|
EventBus.EmitCoinsChanged(Store.GetTotalCoins());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SetCoinsCommand(int amount)
|
private void SetCoinsCommand(int amount)
|
||||||
{
|
{
|
||||||
GameManager.SetCoins(amount);
|
if (Store == null) return;
|
||||||
|
Store.Player.Coins = Mathf.Max(0, amount);
|
||||||
|
EventBus.EmitCoinsChanged(Store.GetTotalCoins());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SetLivesCommand(int amount)
|
private void SetLivesCommand(int amount)
|
||||||
{
|
{
|
||||||
GameManager.SetLives(amount);
|
if (Store == null) return;
|
||||||
|
Store.Player.Lives = amount;
|
||||||
|
EventBus.EmitLivesChanged(amount);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void AddLivesCommand(int amount)
|
private void AddLivesCommand(int amount)
|
||||||
{
|
{
|
||||||
GameManager.AddLives(amount);
|
Store?.AddLives(amount);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SetHealthCommand(float amount)
|
private void SetHealthCommand(float amount)
|
||||||
{
|
{
|
||||||
var playerHealthComponent = GameManager.Player.GetNode<HealthComponent>("HealthComponent");
|
var playerHealthComponent = GameManager?.Player?.GetNode<HealthComponent>("HealthComponent");
|
||||||
if (playerHealthComponent != null)
|
if (playerHealthComponent != null)
|
||||||
{
|
|
||||||
playerHealthComponent.Health = amount;
|
playerHealthComponent.Health = amount;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ResetSessionCommand()
|
private void ResetSessionCommand()
|
||||||
{
|
{
|
||||||
GameManager.ResetCurrentSessionState();
|
Store?.ResetSession();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UnlockSkillCommand(string skillName)
|
private void UnlockSkillCommand(string skillName)
|
||||||
{
|
{
|
||||||
if (!GetSkillManagement()) return;
|
if (!EnsureSkillManagement()) return;
|
||||||
|
|
||||||
var skill = _skillManager.GetSkillByName(skillName);
|
var skill = _skillManager.GetSkillByName(skillName);
|
||||||
if (skill == null)
|
if (skill == null) return;
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
GameManager.UnlockSkill(skill);
|
Store?.UnlockSkillPermanently(skill);
|
||||||
_skillManager.ActivateSkill(skill);
|
_skillManager.ActivateSkill(skill);
|
||||||
_skillUnlockerComponent.EmitSignal(SkillUnlockerComponent.SignalName.SkillUnlocked, skill);
|
_skillUnlockerComponent.EmitSignal(SkillUnlockerComponent.SignalName.SkillUnlocked, skill);
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool GetSkillManagement()
|
|
||||||
{
|
|
||||||
var player = GameManager.Player;
|
|
||||||
if (player == null || !IsInstanceValid(player))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
_skillUnlockerComponent ??= player.GetNode<SkillUnlockerComponent>("SkillUnlockerComponent");
|
|
||||||
|
|
||||||
if (_skillManager != null && _skillUnlockerComponent != null) return true;
|
|
||||||
|
|
||||||
return false;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private void UnlockAllSkillsCommand()
|
private void UnlockAllSkillsCommand()
|
||||||
{
|
{
|
||||||
if (!GetSkillManagement()) return;
|
if (!EnsureSkillManagement()) return;
|
||||||
|
|
||||||
_skillUnlockerComponent.UnlockAllSkills();
|
_skillUnlockerComponent.UnlockAllSkills();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void RemoveSkillCommand(string skillName)
|
private void RemoveSkillCommand(string skillName)
|
||||||
{
|
{
|
||||||
if (!GetSkillManagement()) return;
|
if (!EnsureSkillManagement()) return;
|
||||||
|
|
||||||
var skill = _skillManager.GetSkillByName(skillName);
|
var skill = _skillManager.GetSkillByName(skillName);
|
||||||
if (skill == null)
|
if (skill == null) return;
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
GameManager.RemoveSkill(skill.Name);
|
Store?.RemoveUnlockedSkill(skill.Name);
|
||||||
_skillManager.DeactivateSkill(skill);
|
_skillManager.DeactivateSkill(skill);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void RemoveAllSkillsCommand()
|
private void RemoveAllSkillsCommand()
|
||||||
{
|
{
|
||||||
if (!GetSkillManagement()) return;
|
if (!EnsureSkillManagement()) return;
|
||||||
|
|
||||||
foreach (var skill in _skillManager.AvailableSkills)
|
foreach (var skill in _skillManager.AvailableSkills)
|
||||||
{
|
{
|
||||||
GameManager.RemoveSkill(skill.Name);
|
Store?.RemoveUnlockedSkill(skill.Name);
|
||||||
_skillManager.DeactivateSkill(skill);
|
_skillManager.DeactivateSkill(skill);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void GoToNextLevelCommand()
|
private void GoToNextLevelCommand()
|
||||||
{
|
{
|
||||||
GameManager.OnLevelComplete();
|
GameManager?.OnLevelComplete();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UnlockAchievementCommand(string achievementId)
|
private void UnlockAchievementCommand(string achievementId)
|
||||||
{
|
{
|
||||||
AchievementManager.UnlockAchievement(achievementId);
|
AchievementManager?.UnlockAchievement(achievementId);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ResetAchievementCommand(string achievementId)
|
private void ResetAchievementCommand(string achievementId)
|
||||||
{
|
{
|
||||||
AchievementManager.LockAchievement(achievementId);
|
AchievementManager?.LockAchievement(achievementId);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
private bool EnsureSkillManagement()
|
||||||
|
{
|
||||||
|
var player = GameManager?.Player;
|
||||||
|
if (player == null || !IsInstanceValid(player)) return false;
|
||||||
|
|
||||||
|
_skillUnlockerComponent ??= player.GetNode<SkillUnlockerComponent>("SkillUnlockerComponent");
|
||||||
|
return _skillManager != null && _skillUnlockerComponent != null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -140,6 +140,15 @@ public partial class EventBus : Node
|
|||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
#region Achievement Events
|
||||||
|
|
||||||
|
[Signal] public delegate void AchievementUnlockedEventHandler(string achievementId);
|
||||||
|
|
||||||
|
public static void EmitAchievementUnlocked(string achievementId)
|
||||||
|
=> Instance?.EmitSignal(SignalName.AchievementUnlocked, achievementId);
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
#region State Change Events
|
#region State Change Events
|
||||||
|
|
||||||
[Signal] public delegate void CoinsChangedEventHandler(int totalCoins);
|
[Signal] public delegate void CoinsChangedEventHandler(int totalCoins);
|
||||||
|
|||||||
@@ -1,14 +1,12 @@
|
|||||||
using Godot;
|
using Godot;
|
||||||
using Godot.Collections;
|
using Godot.Collections;
|
||||||
using Mr.BrickAdventures.scripts.components;
|
using Mr.BrickAdventures.scripts.components;
|
||||||
using Mr.BrickAdventures.scripts.Resources;
|
|
||||||
using Mr.BrickAdventures.scripts.State;
|
|
||||||
|
|
||||||
namespace Mr.BrickAdventures.Autoloads;
|
namespace Mr.BrickAdventures.Autoloads;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Game orchestrator - handles scene management and game flow.
|
/// Game orchestrator - handles scene transitions and game flow.
|
||||||
/// State is delegated to GameStateStore for better separation of concerns.
|
/// State lives in GameStateStore; query it directly for reads/writes.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public partial class GameManager : Node
|
public partial class GameManager : Node
|
||||||
{
|
{
|
||||||
@@ -45,150 +43,8 @@ public partial class GameManager : Node
|
|||||||
player.TreeExiting += () => { if (_player == player) _player = null; };
|
player.TreeExiting += () => { if (_player == player) _player = null; };
|
||||||
}
|
}
|
||||||
|
|
||||||
#region Coin Operations
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Adds coins permanently to the player's saved total (Store.Player.Coins).
|
|
||||||
/// Use this for out-of-gameplay grants (e.g. console commands, rewards).
|
|
||||||
/// During active gameplay, coins collected in a level go through
|
|
||||||
/// <see cref="GameStateStore.AddSessionCoins"/> and are only committed on level completion.
|
|
||||||
/// </summary>
|
|
||||||
public void AddCoins(int amount)
|
|
||||||
{
|
|
||||||
if (Store != null)
|
|
||||||
{
|
|
||||||
Store.Player.Coins += amount;
|
|
||||||
EventBus.EmitCoinsChanged(Store.GetTotalCoins());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SetCoins(int amount)
|
|
||||||
{
|
|
||||||
if (Store != null)
|
|
||||||
{
|
|
||||||
Store.Player.Coins = Mathf.Max(0, amount);
|
|
||||||
EventBus.EmitCoinsChanged(Store.GetTotalCoins());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public int GetCoins() => Store?.GetTotalCoins() ?? 0;
|
|
||||||
|
|
||||||
public void RemoveCoins(int amount) => Store?.RemoveCoins(amount);
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Lives Operations
|
|
||||||
|
|
||||||
public void AddLives(int amount) => Store?.AddLives(amount);
|
|
||||||
public void RemoveLives(int amount) => Store?.RemoveLife();
|
|
||||||
public void SetLives(int amount)
|
|
||||||
{
|
|
||||||
if (Store != null)
|
|
||||||
{
|
|
||||||
Store.Player.Lives = amount;
|
|
||||||
EventBus.EmitLivesChanged(amount);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
public int GetLives() => Store?.Player.Lives ?? 0;
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Skill Operations
|
|
||||||
|
|
||||||
public bool IsSkillUnlocked(SkillData skill) => Store?.IsSkillUnlocked(skill) ?? false;
|
|
||||||
|
|
||||||
public void UnlockSkill(SkillData skill)
|
|
||||||
{
|
|
||||||
if (Store != null && !Store.IsSkillUnlocked(skill))
|
|
||||||
{
|
|
||||||
Store.Player.UnlockedSkills.Add(skill);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void RemoveSkill(string skillName)
|
|
||||||
{
|
|
||||||
if (Store == null) return;
|
|
||||||
var skills = Store.Player.UnlockedSkills;
|
|
||||||
for (int i = 0; i < skills.Count; i++)
|
|
||||||
{
|
|
||||||
if (skills[i].Name == skillName)
|
|
||||||
{
|
|
||||||
skills.RemoveAt(i);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void UnlockSkills(Array<SkillData> skills)
|
|
||||||
{
|
|
||||||
foreach (var s in skills)
|
|
||||||
UnlockSkill(s);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Array<SkillData> GetUnlockedSkills()
|
|
||||||
{
|
|
||||||
if (Store == null) return new Array<SkillData>();
|
|
||||||
|
|
||||||
var skills = Store.GetAllUnlockedSkills();
|
|
||||||
var result = new Array<SkillData>();
|
|
||||||
foreach (var s in skills) result.Add(s);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Level Operations
|
|
||||||
|
|
||||||
public void UnlockLevel(int levelIndex) => Store?.UnlockLevel(levelIndex);
|
|
||||||
|
|
||||||
public void MarkLevelComplete(int levelIndex) => Store?.MarkLevelComplete(levelIndex);
|
|
||||||
|
|
||||||
public void TryToGoToNextLevel()
|
|
||||||
{
|
|
||||||
if (Store == null) return;
|
|
||||||
|
|
||||||
var next = Store.Session.CurrentLevel + 1;
|
|
||||||
if (next < LevelScenes.Count && Store.IsLevelUnlocked(next))
|
|
||||||
{
|
|
||||||
Store.Session.CurrentLevel = next;
|
|
||||||
GetTree().ChangeSceneToPacked(LevelScenes[next]);
|
|
||||||
EventBus.EmitLevelStarted(next, GetTree().CurrentScene);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region State Reset
|
|
||||||
|
|
||||||
public void ResetPlayerState() => Store?.ResetAll();
|
|
||||||
|
|
||||||
public void ResetCurrentSessionState() => Store?.ResetSession();
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Game Flow
|
#region Game Flow
|
||||||
|
|
||||||
public void RestartGame()
|
|
||||||
{
|
|
||||||
Store?.ResetAll();
|
|
||||||
GetTree().ChangeSceneToPacked(LevelScenes[0]);
|
|
||||||
SaveSystem.Instance.SaveGame();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void QuitGame() => GetTree().Quit();
|
|
||||||
|
|
||||||
public void PauseGame()
|
|
||||||
{
|
|
||||||
Engine.TimeScale = 0;
|
|
||||||
EventBus.EmitGamePaused();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void ResumeGame()
|
|
||||||
{
|
|
||||||
Engine.TimeScale = 1;
|
|
||||||
EventBus.EmitGameResumed();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void StartNewGame()
|
public void StartNewGame()
|
||||||
{
|
{
|
||||||
Store?.ResetAll();
|
Store?.ResetAll();
|
||||||
@@ -208,7 +64,7 @@ public partial class GameManager : Node
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var idx = Store?.Session.CurrentLevel ?? 0;
|
var idx = Store?.Player.CurrentLevel ?? 0;
|
||||||
if (idx < LevelScenes.Count)
|
if (idx < LevelScenes.Count)
|
||||||
{
|
{
|
||||||
GetTree().ChangeSceneToPacked(LevelScenes[idx]);
|
GetTree().ChangeSceneToPacked(LevelScenes[idx]);
|
||||||
@@ -220,11 +76,18 @@ public partial class GameManager : Node
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void RestartGame()
|
||||||
|
{
|
||||||
|
Store?.ResetAll();
|
||||||
|
GetTree().ChangeSceneToPacked(LevelScenes[0]);
|
||||||
|
SaveSystem.Instance.SaveGame();
|
||||||
|
}
|
||||||
|
|
||||||
public void OnLevelComplete()
|
public void OnLevelComplete()
|
||||||
{
|
{
|
||||||
if (Store == null) return;
|
if (Store == null) return;
|
||||||
|
|
||||||
var levelIndex = Store.Session.CurrentLevel;
|
var levelIndex = Store.Player.CurrentLevel;
|
||||||
Store.MarkLevelComplete(levelIndex);
|
Store.MarkLevelComplete(levelIndex);
|
||||||
Store.CommitSessionCoins();
|
Store.CommitSessionCoins();
|
||||||
Store.CommitSessionSkills();
|
Store.CommitSessionSkills();
|
||||||
@@ -237,6 +100,33 @@ public partial class GameManager : Node
|
|||||||
SaveSystem.Instance.SaveGame();
|
SaveSystem.Instance.SaveGame();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void TryToGoToNextLevel()
|
||||||
|
{
|
||||||
|
if (Store == null) return;
|
||||||
|
|
||||||
|
var next = Store.Player.CurrentLevel + 1;
|
||||||
|
if (next < LevelScenes.Count && Store.IsLevelUnlocked(next))
|
||||||
|
{
|
||||||
|
Store.Player.CurrentLevel = next;
|
||||||
|
GetTree().ChangeSceneToPacked(LevelScenes[next]);
|
||||||
|
EventBus.EmitLevelStarted(next, GetTree().CurrentScene);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void PauseGame()
|
||||||
|
{
|
||||||
|
Engine.TimeScale = 0;
|
||||||
|
EventBus.EmitGamePaused();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ResumeGame()
|
||||||
|
{
|
||||||
|
Engine.TimeScale = 1;
|
||||||
|
EventBus.EmitGameResumed();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void QuitGame() => GetTree().Quit();
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Player Lookup
|
#region Player Lookup
|
||||||
@@ -249,4 +139,4 @@ public partial class GameManager : Node
|
|||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -143,7 +143,7 @@ public partial class GameStateStore : Node
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Unlocks a skill in the session.
|
/// Unlocks a skill in the session (lost on death, committed on level complete).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void UnlockSkillInSession(SkillData skill)
|
public void UnlockSkillInSession(SkillData skill)
|
||||||
{
|
{
|
||||||
@@ -151,6 +151,30 @@ public partial class GameStateStore : Node
|
|||||||
Session.SkillsUnlocked.Add(skill);
|
Session.SkillsUnlocked.Add(skill);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Permanently unlocks a skill directly in player state (bypasses session, e.g. console commands).
|
||||||
|
/// </summary>
|
||||||
|
public void UnlockSkillPermanently(SkillData skill)
|
||||||
|
{
|
||||||
|
if (!Player.UnlockedSkills.Contains(skill))
|
||||||
|
Player.UnlockedSkills.Add(skill);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Removes a permanently unlocked skill from player state by name.
|
||||||
|
/// </summary>
|
||||||
|
public void RemoveUnlockedSkill(string skillName)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < Player.UnlockedSkills.Count; i++)
|
||||||
|
{
|
||||||
|
if (Player.UnlockedSkills[i].Name == skillName)
|
||||||
|
{
|
||||||
|
Player.UnlockedSkills.RemoveAt(i);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Commits session skills to player state.
|
/// Commits session skills to player state.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -179,6 +203,26 @@ public partial class GameStateStore : Node
|
|||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
#region Statistics Operations
|
||||||
|
|
||||||
|
public void IncrementStat(string name, int amount = 1)
|
||||||
|
{
|
||||||
|
if (Player.Statistics.TryGetValue(name, out var current))
|
||||||
|
Player.Statistics[name] = current + amount;
|
||||||
|
else
|
||||||
|
Player.Statistics[name] = amount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetStat(string name, int value) => Player.Statistics[name] = value;
|
||||||
|
|
||||||
|
public int GetStat(string name) =>
|
||||||
|
Player.Statistics.TryGetValue(name, out var value) ? value : 0;
|
||||||
|
|
||||||
|
public System.Collections.Generic.Dictionary<string, int> GetAllStats() =>
|
||||||
|
new(Player.Statistics);
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
#region Reset Operations
|
#region Reset Operations
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Text.Json;
|
||||||
using Godot;
|
using Godot;
|
||||||
using Godot.Collections;
|
|
||||||
using Mr.BrickAdventures.scripts;
|
using Mr.BrickAdventures.scripts;
|
||||||
|
using Mr.BrickAdventures.scripts.State;
|
||||||
|
|
||||||
namespace Mr.BrickAdventures.Autoloads;
|
namespace Mr.BrickAdventures.Autoloads;
|
||||||
|
|
||||||
@@ -24,14 +25,14 @@ public partial class GhostManager : Node
|
|||||||
public void StartRecording(int levelIndex)
|
public void StartRecording(int levelIndex)
|
||||||
{
|
{
|
||||||
if (!IsPlaybackEnabled) return;
|
if (!IsPlaybackEnabled) return;
|
||||||
|
|
||||||
_currentLevelIndex = levelIndex;
|
_currentLevelIndex = levelIndex;
|
||||||
_currentRecording.Clear();
|
_currentRecording.Clear();
|
||||||
_startTime = Time.GetTicksMsec() / 1000.0;
|
_startTime = Time.GetTicksMsec() / 1000.0;
|
||||||
IsRecording = true;
|
IsRecording = true;
|
||||||
GD.Print("Ghost recording started.");
|
GD.Print("Ghost recording started.");
|
||||||
}
|
}
|
||||||
|
|
||||||
public void StopRecording(bool levelCompleted, double finalTime)
|
public void StopRecording(bool levelCompleted, double finalTime)
|
||||||
{
|
{
|
||||||
if (!IsRecording) return;
|
if (!IsRecording) return;
|
||||||
@@ -48,23 +49,22 @@ public partial class GhostManager : Node
|
|||||||
}
|
}
|
||||||
_currentRecording.Clear();
|
_currentRecording.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void RecordFrame(Vector2 position)
|
public void RecordFrame(Vector2 position)
|
||||||
{
|
{
|
||||||
if (!IsRecording) return;
|
if (!IsRecording) return;
|
||||||
|
|
||||||
var frame = new GhostFrame
|
_currentRecording.Add(new GhostFrame
|
||||||
{
|
{
|
||||||
Timestamp = (Time.GetTicksMsec() / 1000.0) - _startTime,
|
Timestamp = (Time.GetTicksMsec() / 1000.0) - _startTime,
|
||||||
Position = position
|
Position = position
|
||||||
};
|
});
|
||||||
_currentRecording.Add(frame);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SpawnGhostPlayer(int levelIndex, Node parent)
|
public void SpawnGhostPlayer(int levelIndex, Node parent)
|
||||||
{
|
{
|
||||||
if (!IsPlaybackEnabled || GhostPlayerScene == null) return;
|
if (!IsPlaybackEnabled || GhostPlayerScene == null) return;
|
||||||
|
|
||||||
var ghostData = LoadGhostData(levelIndex);
|
var ghostData = LoadGhostData(levelIndex);
|
||||||
if (ghostData.Count > 0)
|
if (ghostData.Count > 0)
|
||||||
{
|
{
|
||||||
@@ -74,44 +74,63 @@ public partial class GhostManager : Node
|
|||||||
GD.Print($"Ghost player spawned for level {levelIndex}.");
|
GD.Print($"Ghost player spawned for level {levelIndex}.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SaveGhostData(int levelIndex, double time)
|
private void SaveGhostData(int levelIndex, double time)
|
||||||
{
|
{
|
||||||
var path = $"user://ghost_level_{levelIndex}.dat";
|
var path = $"user://ghost_level_{levelIndex}.json";
|
||||||
using var file = FileAccess.Open(path, FileAccess.ModeFlags.Write);
|
var saveData = new GhostSaveData { BestTime = time };
|
||||||
|
foreach (var frame in _currentRecording)
|
||||||
var dataToSave = new Godot.Collections.Dictionary
|
saveData.Frames.Add(new GhostFrameDto { Timestamp = frame.Timestamp, X = frame.Position.X, Y = frame.Position.Y });
|
||||||
|
|
||||||
|
try
|
||||||
{
|
{
|
||||||
{ "time", time },
|
var json = JsonSerializer.Serialize(saveData, SaveSystem.JsonOptions);
|
||||||
{ "frames", _currentRecording.ToArray() }
|
using var file = FileAccess.Open(path, FileAccess.ModeFlags.Write);
|
||||||
};
|
file.StoreString(json);
|
||||||
file.StoreVar(dataToSave);
|
}
|
||||||
|
catch (System.Exception e)
|
||||||
|
{
|
||||||
|
GD.PrintErr($"GhostManager: Failed to save ghost data: {e.Message}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<GhostFrame> LoadGhostData(int levelIndex)
|
private List<GhostFrame> LoadGhostData(int levelIndex)
|
||||||
{
|
{
|
||||||
var path = $"user://ghost_level_{levelIndex}.dat";
|
var path = $"user://ghost_level_{levelIndex}.json";
|
||||||
if (!FileAccess.FileExists(path)) return [];
|
if (!FileAccess.FileExists(path)) return [];
|
||||||
|
|
||||||
using var file = FileAccess.Open(path, FileAccess.ModeFlags.Read);
|
try
|
||||||
var savedData = (Dictionary)file.GetVar();
|
|
||||||
var framesArray = (Array)savedData["frames"];
|
|
||||||
|
|
||||||
var frames = new List<GhostFrame>();
|
|
||||||
foreach (var obj in framesArray)
|
|
||||||
{
|
{
|
||||||
frames.Add((GhostFrame)obj);
|
using var file = FileAccess.Open(path, FileAccess.ModeFlags.Read);
|
||||||
|
var saveData = JsonSerializer.Deserialize<GhostSaveData>(file.GetAsText(), SaveSystem.JsonOptions);
|
||||||
|
if (saveData == null) return [];
|
||||||
|
|
||||||
|
var frames = new List<GhostFrame>();
|
||||||
|
foreach (var dto in saveData.Frames)
|
||||||
|
frames.Add(new GhostFrame { Timestamp = dto.Timestamp, Position = new Vector2(dto.X, dto.Y) });
|
||||||
|
return frames;
|
||||||
|
}
|
||||||
|
catch (System.Exception e)
|
||||||
|
{
|
||||||
|
GD.PrintErr($"GhostManager: Failed to load ghost data: {e.Message}");
|
||||||
|
return [];
|
||||||
}
|
}
|
||||||
return frames;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private double LoadBestTime(int levelIndex)
|
private double LoadBestTime(int levelIndex)
|
||||||
{
|
{
|
||||||
var path = $"user://ghost_level_{levelIndex}.dat";
|
var path = $"user://ghost_level_{levelIndex}.json";
|
||||||
if (!FileAccess.FileExists(path)) return double.MaxValue;
|
if (!FileAccess.FileExists(path)) return double.MaxValue;
|
||||||
|
|
||||||
using var file = FileAccess.Open(path, FileAccess.ModeFlags.Read);
|
try
|
||||||
var data = (Dictionary)file.GetVar();
|
{
|
||||||
return (double)data["time"];
|
using var file = FileAccess.Open(path, FileAccess.ModeFlags.Read);
|
||||||
|
var saveData = JsonSerializer.Deserialize<GhostSaveData>(file.GetAsText(), SaveSystem.JsonOptions);
|
||||||
|
return saveData?.BestTime ?? double.MaxValue;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return double.MaxValue;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ public partial class SaveSystem : Node
|
|||||||
|
|
||||||
public static SaveSystem Instance { get; private set; }
|
public static SaveSystem Instance { get; private set; }
|
||||||
|
|
||||||
private static readonly JsonSerializerOptions JsonOptions = new()
|
internal static readonly JsonSerializerOptions JsonOptions = new()
|
||||||
{
|
{
|
||||||
WriteIndented = true,
|
WriteIndented = true,
|
||||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
|
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
|
||||||
@@ -47,7 +47,7 @@ public partial class SaveSystem : Node
|
|||||||
Version = Version,
|
Version = Version,
|
||||||
Coins = store.Player.Coins,
|
Coins = store.Player.Coins,
|
||||||
Lives = store.Player.Lives,
|
Lives = store.Player.Lives,
|
||||||
CurrentLevel = store.Session.CurrentLevel,
|
CurrentLevel = store.Player.CurrentLevel,
|
||||||
CompletedLevels = [.. store.Player.CompletedLevels],
|
CompletedLevels = [.. store.Player.CompletedLevels],
|
||||||
UnlockedLevels = new List<int>(store.Player.UnlockedLevels),
|
UnlockedLevels = new List<int>(store.Player.UnlockedLevels),
|
||||||
UnlockedSkillNames = GetSkillNames(store.Player.UnlockedSkills),
|
UnlockedSkillNames = GetSkillNames(store.Player.UnlockedSkills),
|
||||||
@@ -105,7 +105,7 @@ public partial class SaveSystem : Node
|
|||||||
// Apply loaded state
|
// Apply loaded state
|
||||||
store.Player.Coins = saveData.Coins;
|
store.Player.Coins = saveData.Coins;
|
||||||
store.Player.Lives = saveData.Lives;
|
store.Player.Lives = saveData.Lives;
|
||||||
store.Session.CurrentLevel = saveData.CurrentLevel;
|
store.Player.CurrentLevel = saveData.CurrentLevel;
|
||||||
|
|
||||||
store.Player.CompletedLevels = saveData.CompletedLevels ?? new List<int>();
|
store.Player.CompletedLevels = saveData.CompletedLevels ?? new List<int>();
|
||||||
store.Player.UnlockedLevels = saveData.UnlockedLevels ?? new List<int> { 0 };
|
store.Player.UnlockedLevels = saveData.UnlockedLevels ?? new List<int> { 0 };
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ namespace Mr.BrickAdventures.Autoloads;
|
|||||||
public partial class SkillManager : Node
|
public partial class SkillManager : Node
|
||||||
{
|
{
|
||||||
private PlayerController _player;
|
private PlayerController _player;
|
||||||
private GameManager GameManager => GameManager.Instance;
|
private GameStateStore Store => GameStateStore.Instance;
|
||||||
|
|
||||||
[Export] public Array<SkillData> AvailableSkills { get; set; } = [];
|
[Export] public Array<SkillData> AvailableSkills { get; set; } = [];
|
||||||
|
|
||||||
@@ -154,18 +154,14 @@ public partial class SkillManager : Node
|
|||||||
public void ApplyUnlockedSkills()
|
public void ApplyUnlockedSkills()
|
||||||
{
|
{
|
||||||
if (_player == null || !IsInstanceValid(_player)) return;
|
if (_player == null || !IsInstanceValid(_player)) return;
|
||||||
if (GameManager == null) return;
|
if (Store == null) return;
|
||||||
|
|
||||||
foreach (var sd in AvailableSkills)
|
foreach (var sd in AvailableSkills)
|
||||||
{
|
{
|
||||||
if (GameManager.IsSkillUnlocked(sd))
|
if (Store.IsSkillUnlocked(sd))
|
||||||
{
|
|
||||||
CallDeferred(MethodName.AddSkill, sd);
|
CallDeferred(MethodName.AddSkill, sd);
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
|
||||||
RemoveSkill(sd.Name);
|
RemoveSkill(sd.Name);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,66 +0,0 @@
|
|||||||
using System.Collections.Generic;
|
|
||||||
using Godot;
|
|
||||||
using Mr.BrickAdventures.scripts.State;
|
|
||||||
|
|
||||||
namespace Mr.BrickAdventures.Autoloads;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Manages game statistics using GameStateStore.
|
|
||||||
/// </summary>
|
|
||||||
public partial class StatisticsManager : Node
|
|
||||||
{
|
|
||||||
public static StatisticsManager Instance { get; private set; }
|
|
||||||
|
|
||||||
public override void _Ready() => Instance = this;
|
|
||||||
public override void _ExitTree() { if (Instance == this) Instance = null; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the statistics dictionary from the store.
|
|
||||||
/// </summary>
|
|
||||||
private Dictionary<string, int> GetStats()
|
|
||||||
{
|
|
||||||
return GameStateStore.Instance?.Player.Statistics ?? new Dictionary<string, int>();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Increases a numerical statistic by a given amount.
|
|
||||||
/// </summary>
|
|
||||||
public void IncrementStat(string statName, int amount = 1)
|
|
||||||
{
|
|
||||||
var stats = GetStats();
|
|
||||||
if (stats.TryGetValue(statName, out var currentValue))
|
|
||||||
{
|
|
||||||
stats[statName] = currentValue + amount;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
stats[statName] = amount;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Sets a statistic to a specific value.
|
|
||||||
/// </summary>
|
|
||||||
public void SetStat(string statName, int value)
|
|
||||||
{
|
|
||||||
var stats = GetStats();
|
|
||||||
stats[statName] = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the value of a statistic.
|
|
||||||
/// </summary>
|
|
||||||
public int GetStat(string statName)
|
|
||||||
{
|
|
||||||
var stats = GetStats();
|
|
||||||
return stats.TryGetValue(statName, out var value) ? value : 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets a copy of all statistics.
|
|
||||||
/// </summary>
|
|
||||||
public Dictionary<string, int> GetAllStats()
|
|
||||||
{
|
|
||||||
return new Dictionary<string, int>(GetStats());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
uid://c5p3l2mhkw0p4
|
|
||||||
@@ -46,12 +46,10 @@ SteamManager="*res://Autoloads/SteamManager.cs"
|
|||||||
AchievementManager="*res://objects/achievement_manager.tscn"
|
AchievementManager="*res://objects/achievement_manager.tscn"
|
||||||
SkillManager="*res://objects/skill_manager.tscn"
|
SkillManager="*res://objects/skill_manager.tscn"
|
||||||
FloatingTextManager="*res://objects/floating_text_manager.tscn"
|
FloatingTextManager="*res://objects/floating_text_manager.tscn"
|
||||||
StatisticsManager="*res://Autoloads/StatisticsManager.cs"
|
|
||||||
SpeedRunManager="res://Autoloads/SpeedRunManager.cs"
|
SpeedRunManager="res://Autoloads/SpeedRunManager.cs"
|
||||||
GhostManager="res://objects/ghost_manager.tscn"
|
GhostManager="res://objects/ghost_manager.tscn"
|
||||||
StatisticsEventHandler="*res://scripts/Events/StatisticsEventHandler.cs"
|
StatisticsEventHandler="*res://scripts/Events/StatisticsEventHandler.cs"
|
||||||
CoinStateHandler="*res://scripts/Events/CoinStateHandler.cs"
|
CoinStateHandler="*res://scripts/Events/CoinStateHandler.cs"
|
||||||
LevelStateHandler="*res://scripts/Events/LevelStateHandler.cs"
|
|
||||||
LivesStateHandler="*res://scripts/Events/LivesStateHandler.cs"
|
LivesStateHandler="*res://scripts/Events/LivesStateHandler.cs"
|
||||||
SkillCollectHandler="*res://scripts/Events/SkillCollectHandler.cs"
|
SkillCollectHandler="*res://scripts/Events/SkillCollectHandler.cs"
|
||||||
GameStateStore="*res://Autoloads/GameStateStore.cs"
|
GameStateStore="*res://Autoloads/GameStateStore.cs"
|
||||||
|
|||||||
@@ -15,8 +15,11 @@ public partial class GhostEventHandler : Node
|
|||||||
|
|
||||||
public override void _ExitTree()
|
public override void _ExitTree()
|
||||||
{
|
{
|
||||||
EventBus.Instance.LevelStarted -= OnLevelStarted;
|
if (EventBus.Instance != null)
|
||||||
EventBus.Instance.LevelCompleted -= OnLevelCompleted;
|
{
|
||||||
|
EventBus.Instance.LevelStarted -= OnLevelStarted;
|
||||||
|
EventBus.Instance.LevelCompleted -= OnLevelCompleted;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnLevelStarted(int levelIndex, Node currentScene)
|
private void OnLevelStarted(int levelIndex, Node currentScene)
|
||||||
|
|||||||
@@ -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.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
uid://gx5vn7viphv
|
|
||||||
@@ -33,7 +33,6 @@ public partial class SkillCollectHandler : Node
|
|||||||
GameStateStore.Instance?.UnlockSkillInSession(skill);
|
GameStateStore.Instance?.UnlockSkillInSession(skill);
|
||||||
|
|
||||||
// Immediately activate the skill for the player
|
// Immediately activate the skill for the player
|
||||||
skill.Level = 1;
|
|
||||||
SkillManager?.AddSkill(skill);
|
SkillManager?.AddSkill(skill);
|
||||||
|
|
||||||
// Emit skill unlocked event for UI/achievements
|
// Emit skill unlocked event for UI/achievements
|
||||||
|
|||||||
@@ -13,7 +13,8 @@ public partial class SpeedRunEventHandler : Node
|
|||||||
|
|
||||||
public override void _ExitTree()
|
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)
|
private void OnLevelCompleted(int levelIndex, Node currentScene, double completionTime)
|
||||||
|
|||||||
@@ -1,19 +1,16 @@
|
|||||||
using Godot;
|
using Godot;
|
||||||
using Mr.BrickAdventures.Autoloads;
|
using Mr.BrickAdventures.Autoloads;
|
||||||
|
using Mr.BrickAdventures.scripts.State;
|
||||||
|
|
||||||
namespace Mr.BrickAdventures.scripts.Events;
|
namespace Mr.BrickAdventures.scripts.Events;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Handles game events and updates statistics accordingly.
|
/// Handles game events and updates statistics accordingly.
|
||||||
/// Listens to EventBus signals and increments relevant stats.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public partial class StatisticsEventHandler : Node
|
public partial class StatisticsEventHandler : Node
|
||||||
{
|
{
|
||||||
private StatisticsManager StatisticsManager => StatisticsManager.Instance;
|
|
||||||
|
|
||||||
public override void _Ready()
|
public override void _Ready()
|
||||||
{
|
{
|
||||||
// Subscribe to events
|
|
||||||
EventBus.Instance.CoinCollected += OnCoinCollected;
|
EventBus.Instance.CoinCollected += OnCoinCollected;
|
||||||
EventBus.Instance.EnemyDefeated += OnEnemyDefeated;
|
EventBus.Instance.EnemyDefeated += OnEnemyDefeated;
|
||||||
EventBus.Instance.PlayerDied += OnPlayerDied;
|
EventBus.Instance.PlayerDied += OnPlayerDied;
|
||||||
@@ -33,27 +30,17 @@ public partial class StatisticsEventHandler : Node
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void OnCoinCollected(int amount, Vector2 position)
|
private void OnCoinCollected(int amount, Vector2 position)
|
||||||
{
|
=> GameStateStore.Instance?.IncrementStat(StatNames.CoinsCollected, amount);
|
||||||
StatisticsManager.IncrementStat("coins_collected", amount);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnEnemyDefeated(Node enemy, Vector2 position)
|
private void OnEnemyDefeated(Node enemy, Vector2 position)
|
||||||
{
|
=> GameStateStore.Instance?.IncrementStat(StatNames.EnemiesDefeated);
|
||||||
StatisticsManager.IncrementStat("enemies_defeated");
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnPlayerDied(Vector2 position)
|
private void OnPlayerDied(Vector2 position)
|
||||||
{
|
=> GameStateStore.Instance?.IncrementStat(StatNames.Deaths);
|
||||||
StatisticsManager.IncrementStat("deaths");
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnLevelCompleted(int levelIndex, Node currentScene, double completionTime)
|
private void OnLevelCompleted(int levelIndex, Node currentScene, double completionTime)
|
||||||
{
|
=> GameStateStore.Instance?.IncrementStat(StatNames.LevelsCompleted);
|
||||||
StatisticsManager.IncrementStat("levels_completed");
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnChildRescued(Vector2 position)
|
private void OnChildRescued(Vector2 position)
|
||||||
{
|
=> GameStateStore.Instance?.IncrementStat(StatNames.ChildrenRescued);
|
||||||
StatisticsManager.IncrementStat("children_rescued");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
16
scripts/State/GhostSaveData.cs
Normal file
16
scripts/State/GhostSaveData.cs
Normal 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; }
|
||||||
|
}
|
||||||
1
scripts/State/GhostSaveData.cs.uid
Normal file
1
scripts/State/GhostSaveData.cs.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://drp68lkok8if3
|
||||||
@@ -11,6 +11,11 @@ public class PlayerState
|
|||||||
{
|
{
|
||||||
private const int DefaultLives = 3;
|
private const int DefaultLives = 3;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The level index the player is currently on. Persisted across sessions.
|
||||||
|
/// </summary>
|
||||||
|
public int CurrentLevel { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Saved coins (not including current session).
|
/// Saved coins (not including current session).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -56,6 +61,7 @@ public class PlayerState
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public void Reset()
|
public void Reset()
|
||||||
{
|
{
|
||||||
|
CurrentLevel = 0;
|
||||||
Coins = 0;
|
Coins = 0;
|
||||||
Lives = DefaultLives;
|
Lives = DefaultLives;
|
||||||
CompletedLevels.Clear();
|
CompletedLevels.Clear();
|
||||||
|
|||||||
@@ -9,11 +9,6 @@ namespace Mr.BrickAdventures.scripts.State;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class SessionState
|
public class SessionState
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// Current level index being played.
|
|
||||||
/// </summary>
|
|
||||||
public int CurrentLevel { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Coins collected during this session (not yet saved).
|
/// Coins collected during this session (not yet saved).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -29,7 +24,6 @@ public class SessionState
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static SessionState CreateDefault() => new()
|
public static SessionState CreateDefault() => new()
|
||||||
{
|
{
|
||||||
CurrentLevel = 0,
|
|
||||||
CoinsCollected = 0,
|
CoinsCollected = 0,
|
||||||
SkillsUnlocked = new List<SkillData>()
|
SkillsUnlocked = new List<SkillData>()
|
||||||
};
|
};
|
||||||
@@ -44,11 +38,10 @@ public class SessionState
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Resets completely including level.
|
/// Resets all session state.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void ResetAll()
|
public void ResetAll()
|
||||||
{
|
{
|
||||||
CurrentLevel = 0;
|
|
||||||
CoinsCollected = 0;
|
CoinsCollected = 0;
|
||||||
SkillsUnlocked.Clear();
|
SkillsUnlocked.Clear();
|
||||||
}
|
}
|
||||||
|
|||||||
10
scripts/State/StatNames.cs
Normal file
10
scripts/State/StatNames.cs
Normal 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";
|
||||||
|
}
|
||||||
1
scripts/State/StatNames.cs.uid
Normal file
1
scripts/State/StatNames.cs.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://dtqjh4v5w41ni
|
||||||
@@ -11,29 +11,26 @@ public partial class GameOverScreen : Control
|
|||||||
[Export] public Button MainMenuButton { get; set; }
|
[Export] public Button MainMenuButton { get; set; }
|
||||||
[Export] public PackedScene MainMenuScene { get; set; }
|
[Export] public PackedScene MainMenuScene { get; set; }
|
||||||
|
|
||||||
private GameManager _gameManager;
|
|
||||||
|
|
||||||
public override void _Ready()
|
public override void _Ready()
|
||||||
{
|
{
|
||||||
_gameManager = GameManager.Instance;
|
|
||||||
RestartButton.Pressed += OnRestartClicked;
|
RestartButton.Pressed += OnRestartClicked;
|
||||||
MainMenuButton.Pressed += OnMainMenuClicked;
|
MainMenuButton.Pressed += OnMainMenuClicked;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnMainMenuClicked()
|
private void OnMainMenuClicked()
|
||||||
{
|
{
|
||||||
_gameManager.ResetPlayerState();
|
GameStateStore.Instance?.ResetAll();
|
||||||
GetTree().ChangeSceneToPacked(MainMenuScene);
|
GetTree().ChangeSceneToPacked(MainMenuScene);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnRestartClicked()
|
private void OnRestartClicked()
|
||||||
{
|
{
|
||||||
_gameManager.RestartGame();
|
GameManager.Instance?.RestartGame();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void OnPlayerDeath()
|
public void OnPlayerDeath()
|
||||||
{
|
{
|
||||||
if (_gameManager == null || _gameManager.GetLives() != 0) return;
|
if (GameStateStore.Instance?.Player.Lives != 0) return;
|
||||||
|
|
||||||
GameOverPanel.Show();
|
GameOverPanel.Show();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,12 +12,7 @@ public partial class Hud : Control
|
|||||||
[Export] public ProgressBar HealthBar { get; set; }
|
[Export] public ProgressBar HealthBar { get; set; }
|
||||||
[Export] public Label LivesLabel { get; set; }
|
[Export] public Label LivesLabel { get; set; }
|
||||||
|
|
||||||
private GameManager _gameManager;
|
private GameStateStore Store => GameStateStore.Instance;
|
||||||
|
|
||||||
public override void _Ready()
|
|
||||||
{
|
|
||||||
_gameManager = GameManager.Instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void _Process(double delta)
|
public override void _Process(double delta)
|
||||||
{
|
{
|
||||||
@@ -28,12 +23,12 @@ public partial class Hud : Control
|
|||||||
|
|
||||||
private void SetCoinsLabel()
|
private void SetCoinsLabel()
|
||||||
{
|
{
|
||||||
CoinsLabel.Text = Tr("COINS_LABEL") + ": " + _gameManager.GetCoins();
|
CoinsLabel.Text = Tr("COINS_LABEL") + ": " + (Store?.GetTotalCoins() ?? 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SetLivesLabel()
|
private void SetLivesLabel()
|
||||||
{
|
{
|
||||||
LivesLabel.Text = Tr("LIVES_LABEL") + ": " + _gameManager.GetLives();
|
LivesLabel.Text = Tr("LIVES_LABEL") + ": " + (Store?.Player.Lives ?? 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SetHealthBar()
|
private void SetHealthBar()
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ public partial class Marketplace : Control
|
|||||||
[Export] public PackedScene MarketplaceButtonScene { get; set; }
|
[Export] public PackedScene MarketplaceButtonScene { get; set; }
|
||||||
[Export] public PackedScene SkillButtonScene { get; set; }
|
[Export] public PackedScene SkillButtonScene { get; set; }
|
||||||
|
|
||||||
private GameManager GameManager => GameManager.Instance;
|
private GameStateStore Store => GameStateStore.Instance;
|
||||||
private SkillManager SkillManager => SkillManager.Instance;
|
private SkillManager SkillManager => SkillManager.Instance;
|
||||||
private readonly List<Button> _unlockButtons = [];
|
private readonly List<Button> _unlockButtons = [];
|
||||||
private readonly List<SkillButton> _skillButtons = [];
|
private readonly List<SkillButton> _skillButtons = [];
|
||||||
@@ -33,7 +33,7 @@ public partial class Marketplace : Control
|
|||||||
|
|
||||||
foreach (var skill in skillsToUnlock) CreateUpgradeButton(skill);
|
foreach (var skill in skillsToUnlock) CreateUpgradeButton(skill);
|
||||||
|
|
||||||
var unlockedSkills = GameManager.GetUnlockedSkills();
|
var unlockedSkills = Store.GetAllUnlockedSkills();
|
||||||
foreach (var skill in unlockedSkills) CreateSkillButton(skill);
|
foreach (var skill in unlockedSkills) CreateSkillButton(skill);
|
||||||
|
|
||||||
SkillUnlockerComponent.SkillUnlocked += OnSkillUnlocked;
|
SkillUnlockerComponent.SkillUnlocked += OnSkillUnlocked;
|
||||||
@@ -113,7 +113,7 @@ public partial class Marketplace : Control
|
|||||||
|
|
||||||
private void OnUpgradeButtonPressed(SkillData skill)
|
private void OnUpgradeButtonPressed(SkillData skill)
|
||||||
{
|
{
|
||||||
if (GameManager.IsSkillUnlocked(skill))
|
if (Store.IsSkillUnlocked(skill))
|
||||||
{
|
{
|
||||||
if (skill.Level < skill.MaxLevel)
|
if (skill.Level < skill.MaxLevel)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -13,14 +13,12 @@ public partial class MarketplaceButton : Button
|
|||||||
[Export] public Texture2D LockedSkillIcon { get; set; }
|
[Export] public Texture2D LockedSkillIcon { get; set; }
|
||||||
[Export] public Container SkillLevelContainer { get; set; }
|
[Export] public Container SkillLevelContainer { get; set; }
|
||||||
|
|
||||||
private GameManager _gameManager;
|
|
||||||
private SkillUnlockerComponent _skillUnlockerComponent;
|
private SkillUnlockerComponent _skillUnlockerComponent;
|
||||||
private SkillManager _skillManager;
|
private SkillManager _skillManager;
|
||||||
|
|
||||||
public override void _Ready()
|
public override void _Ready()
|
||||||
{
|
{
|
||||||
_gameManager = GameManager.Instance;
|
var player = GameManager.Instance?.Player;
|
||||||
var player = _gameManager.Player;
|
|
||||||
if (player == null) return;
|
if (player == null) return;
|
||||||
|
|
||||||
_skillUnlockerComponent = player.GetNodeOrNull<SkillUnlockerComponent>("SkillUnlockerComponent");
|
_skillUnlockerComponent = player.GetNodeOrNull<SkillUnlockerComponent>("SkillUnlockerComponent");
|
||||||
@@ -59,7 +57,7 @@ public partial class MarketplaceButton : Button
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var isUnlocked = _gameManager.IsSkillUnlocked(Data);
|
var isUnlocked = GameStateStore.Instance?.IsSkillUnlocked(Data) ?? false;
|
||||||
|
|
||||||
for (var i = 0; i < SkillLevelContainer.GetChildCount(); i++)
|
for (var i = 0; i < SkillLevelContainer.GetChildCount(); i++)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ public partial class PauseMenu : Control
|
|||||||
private void OnMainMenuPressed()
|
private void OnMainMenuPressed()
|
||||||
{
|
{
|
||||||
GameManager.ResumeGame();
|
GameManager.ResumeGame();
|
||||||
GameManager.ResetCurrentSessionState();
|
GameStateStore.Instance?.ResetSession();
|
||||||
GetTree().ChangeSceneToPacked(MainMenuScene);
|
GetTree().ChangeSceneToPacked(MainMenuScene);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,13 +12,11 @@ public partial class BrickShieldSkillComponent : SkillComponentBase
|
|||||||
[Export] public PackedScene ShieldScene { get; set; }
|
[Export] public PackedScene ShieldScene { get; set; }
|
||||||
|
|
||||||
private Node2D _shieldInstance;
|
private Node2D _shieldInstance;
|
||||||
private GameManager _gameManager;
|
|
||||||
private SkillManager _skillManager;
|
private SkillManager _skillManager;
|
||||||
private HealthComponent _shieldHealth;
|
private HealthComponent _shieldHealth;
|
||||||
|
|
||||||
public override void _Ready()
|
public override void _Ready()
|
||||||
{
|
{
|
||||||
_gameManager = GameManager.Instance;
|
|
||||||
_skillManager = SkillManager.Instance;
|
_skillManager = SkillManager.Instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -55,9 +53,9 @@ public partial class BrickShieldSkillComponent : SkillComponentBase
|
|||||||
|
|
||||||
private void OnShieldDestroyed()
|
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);
|
_skillManager.RemoveSkill(Data.Name);
|
||||||
}
|
}
|
||||||
_shieldInstance = null;
|
_shieldInstance = null;
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
using Godot;
|
using Godot;
|
||||||
using Mr.BrickAdventures.Autoloads;
|
using Mr.BrickAdventures.Autoloads;
|
||||||
using Mr.BrickAdventures.scripts.interfaces;
|
using Mr.BrickAdventures.scripts.interfaces;
|
||||||
using Mr.BrickAdventures.scripts.State;
|
|
||||||
|
|
||||||
namespace Mr.BrickAdventures.scripts.components;
|
namespace Mr.BrickAdventures.scripts.components;
|
||||||
|
|
||||||
@@ -28,8 +27,6 @@ public partial class ExitDoorComponent : Area2D, IUnlockable
|
|||||||
|
|
||||||
EmitSignalExitTriggered();
|
EmitSignalExitTriggered();
|
||||||
AchievementManager.Instance?.UnlockAchievement(AchievementId);
|
AchievementManager.Instance?.UnlockAchievement(AchievementId);
|
||||||
var currentLevel = GameStateStore.Instance?.Session.CurrentLevel ?? 0;
|
|
||||||
GameManager.Instance?.UnlockLevel(currentLevel + 1);
|
|
||||||
CallDeferred(nameof(GoToNextLevel));
|
CallDeferred(nameof(GoToNextLevel));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,8 @@
|
|||||||
using Godot;
|
using Godot;
|
||||||
using Godot.Collections;
|
using Godot.Collections;
|
||||||
using Mr.BrickAdventures;
|
|
||||||
using Mr.BrickAdventures.Autoloads;
|
using Mr.BrickAdventures.Autoloads;
|
||||||
using Mr.BrickAdventures.scripts.interfaces;
|
using Mr.BrickAdventures.scripts.interfaces;
|
||||||
using Mr.BrickAdventures.scripts.Resources;
|
using Mr.BrickAdventures.scripts.Resources;
|
||||||
using Mr.BrickAdventures.scripts.State;
|
|
||||||
|
|
||||||
namespace Mr.BrickAdventures.scripts.components;
|
namespace Mr.BrickAdventures.scripts.components;
|
||||||
|
|
||||||
@@ -16,30 +14,24 @@ public partial class SkillUnlockerComponent : Node
|
|||||||
[Signal]
|
[Signal]
|
||||||
public delegate void SkillUnlockedEventHandler(SkillData skill);
|
public delegate void SkillUnlockedEventHandler(SkillData skill);
|
||||||
|
|
||||||
private GameManager _gameManager;
|
private GameStateStore Store => GameStateStore.Instance;
|
||||||
|
|
||||||
public override void _Ready()
|
public override void _Ready()
|
||||||
{
|
{
|
||||||
_gameManager = GameManager.Instance;
|
|
||||||
SkillManager = SkillManager.Instance;
|
SkillManager = SkillManager.Instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool HasEnoughCoins(int amount)
|
private bool HasEnoughCoins(int amount) => (Store?.GetTotalCoins() ?? 0) >= amount;
|
||||||
{
|
|
||||||
return _gameManager != null && _gameManager.GetCoins() >= amount;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool TryUnlockSkill(SkillData skill)
|
public bool TryUnlockSkill(SkillData skill)
|
||||||
{
|
{
|
||||||
if (_gameManager == null) return false;
|
if (Store == null) return false;
|
||||||
if (_gameManager.IsSkillUnlocked(skill)) return false;
|
if (Store.IsSkillUnlocked(skill)) return false;
|
||||||
if (!HasEnoughCoins(skill.Upgrades[0].Cost)) return false;
|
if (!HasEnoughCoins(skill.Upgrades[0].Cost)) return false;
|
||||||
|
|
||||||
skill.Level = 1;
|
skill.Level = 1;
|
||||||
_gameManager.RemoveCoins(skill.Upgrades[0].Cost);
|
Store.RemoveCoins(skill.Upgrades[0].Cost);
|
||||||
|
Store.UnlockSkillInSession(skill);
|
||||||
// Add to session state via GameStateStore
|
|
||||||
GameStateStore.Instance?.UnlockSkillInSession(skill);
|
|
||||||
SkillManager.AddSkill(skill);
|
SkillManager.AddSkill(skill);
|
||||||
EmitSignalSkillUnlocked(skill);
|
EmitSignalSkillUnlocked(skill);
|
||||||
|
|
||||||
@@ -52,31 +44,29 @@ public partial class SkillUnlockerComponent : Node
|
|||||||
|
|
||||||
foreach (var skill in availableSkills)
|
foreach (var skill in availableSkills)
|
||||||
{
|
{
|
||||||
|
Store?.UnlockSkillPermanently(skill);
|
||||||
EmitSignalSkillUnlocked(skill);
|
EmitSignalSkillUnlocked(skill);
|
||||||
}
|
}
|
||||||
|
|
||||||
_gameManager.UnlockSkills(availableSkills);
|
|
||||||
SkillManager.ApplyUnlockedSkills();
|
SkillManager.ApplyUnlockedSkills();
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool TryUpgradeSkill(SkillData skill)
|
public bool TryUpgradeSkill(SkillData skill)
|
||||||
{
|
{
|
||||||
if (_gameManager == null) return false;
|
if (Store == null) return false;
|
||||||
if (!_gameManager.IsSkillUnlocked(skill)) return false;
|
if (!Store.IsSkillUnlocked(skill)) return false;
|
||||||
if (skill.Level >= skill.MaxLevel) return false;
|
if (skill.Level >= skill.MaxLevel) return false;
|
||||||
if (!HasEnoughCoins(skill.Upgrades[skill.Level].Cost)) 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++;
|
skill.Level++;
|
||||||
if (SkillManager.ActiveComponents.TryGetValue(skill.Name, out Variant componentVariant))
|
if (SkillManager.ActiveComponents.TryGetValue(skill.Name, out Variant componentVariant))
|
||||||
{
|
{
|
||||||
var component = componentVariant.AsGodotObject();
|
var component = componentVariant.AsGodotObject();
|
||||||
if (component is ISkill skillInstance)
|
if (component is ISkill skillInstance)
|
||||||
{
|
|
||||||
skillInstance.ApplyUpgrade(skill.Upgrades[skill.Level - 1]);
|
skillInstance.ApplyUpgrade(skill.Upgrades[skill.Level - 1]);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
EmitSignalSkillUnlocked(skill);
|
EmitSignalSkillUnlocked(skill);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user