feat: coins and skills move to the new domain (commit B of the swap)

atomic pair - purchasing couples them, so neither moves alone.

SkillData.Level is gone. It was an [Export] on a cached Resource: one
instance per process, so the level never persisted, leaked between save
slots, and made "unowned" indistinguishable from "level 1". Level now
comes from the Playthrough and is passed to ISkill.Initialize.

SkillManager reconciles from SkillSetChanged instead of being told by
each caller. The old shape was "record it, then tell SkillManager" as two
calls at four sites; AddSkill's early return when no player is registered
left the skill owned but inert. Reconciliation also tracks the level each
component was built at, so an upgrade rebuilds it rather than being
patched in from outside.

SkillUnlockerComponent collapses to one TryBuy - unlock and upgrade were
two methods each doing affordability, debit, level mutation and manager
notification in order.

The guard three components copy-pasted around Upgrades[Level - 1] - and
which BrickArmor omitted - now lives once in SkillComponentBase.

Hud reads coins from CoinsChanged instead of rebuilding translated
strings every frame. Lives still polls; it moves with the death path.
This commit is contained in:
2026-07-28 20:13:24 +02:00
parent b6a8448743
commit 360f73ba24
17 changed files with 251 additions and 238 deletions

View File

@@ -1,17 +1,20 @@
using Godot;
using Mr.BrickAdventures.Autoloads;
using Mr.BrickAdventures.Progression;
using Mr.BrickAdventures.scripts.Resources;
namespace Mr.BrickAdventures.scripts.Events;
/// <summary>
/// Handles skill collection events and unlocks skills via GameStateStore.
/// Skills are immediately activated but only persisted on level complete.
/// Grants a skill picked up in a level.
/// </summary>
/// <remarks>
/// Only records that it is owned. Attaching the live component is SkillManager's job, driven by
/// SkillSetChanged — previously this did both as two separate calls, and the second could silently do
/// nothing if no player was registered yet, leaving the skill owned but inert.
/// </remarks>
public partial class SkillCollectHandler : Node
{
private SkillManager SkillManager => SkillManager.Instance;
public override void _Ready()
{
EventBus.Instance.SkillCollected += OnSkillCollected;
@@ -20,22 +23,13 @@ public partial class SkillCollectHandler : Node
public override void _ExitTree()
{
if (EventBus.Instance != null)
{
EventBus.Instance.SkillCollected -= OnSkillCollected;
}
}
private void OnSkillCollected(SkillData skill, Vector2 position)
{
if (skill == null) return;
if (skill == null || string.IsNullOrWhiteSpace(skill.Name)) return;
// Unlock in session (will be committed on level complete, lost on death)
GameStateStore.Instance?.UnlockSkillInSession(skill);
// Immediately activate the skill for the player
SkillManager?.AddSkill(skill);
// Emit skill unlocked event for UI/achievements
EventBus.EmitSkillUnlocked(skill.Name, skill.Level);
ProgressionRoot.Instance?.GrantSkill(SkillId.Of(skill.Name));
}
}