Files
przygody-pana-cegly/scripts/UI/MarketplaceButton.cs
Gabriel Kaszewski 360f73ba24 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.
2026-07-28 20:13:24 +02:00

90 lines
2.8 KiB
C#

using Godot;
using Mr.BrickAdventures.Autoloads;
using Mr.BrickAdventures.Progression;
using Mr.BrickAdventures.scripts.Resources;
namespace Mr.BrickAdventures.scripts.UI;
/// <summary>
/// One buy/upgrade button in the marketplace.
/// </summary>
/// <remarks>
/// Reads the owned level from the Playthrough and the costs from the authored upgrade table — the split
/// <c>SkillData.Level</c> used to blur. It also listens to <see cref="EventBus.SkillSetChanged"/> rather
/// than to a signal on a component it had to locate by node path on the player.
/// </remarks>
public partial class MarketplaceButton : Button
{
[Export] public SkillData Data { get; set; }
[Export] public Texture2D UnlockedSkillIcon { get; set; }
[Export] public Texture2D LockedSkillIcon { get; set; }
[Export] public Container SkillLevelContainer { get; set; }
private static ProgressionRoot Progression => ProgressionRoot.Instance;
public override void _Ready()
{
if (EventBus.Instance != null)
{
EventBus.Instance.SkillSetChanged += UpdateButtonState;
EventBus.Instance.CoinsChanged += OnCoinsChanged;
}
UpdateButtonState();
}
public override void _ExitTree()
{
if (EventBus.Instance != null)
{
EventBus.Instance.SkillSetChanged -= UpdateButtonState;
EventBus.Instance.CoinsChanged -= OnCoinsChanged;
}
}
private void OnCoinsChanged(int totalCoins) => UpdateButtonState();
/// <summary>The level the player owns this skill at, or 0 when they do not own it.</summary>
private int OwnedLevel()
{
if (Data == null || Progression == null || string.IsNullOrWhiteSpace(Data.Name)) return 0;
return Progression.LevelOf(SkillId.Of(Data.Name))?.Value ?? 0;
}
private void UpdateButtonState()
{
if (Data == null || Data.Upgrades.Count == 0)
{
// No authored upgrades means it cannot be bought at any price.
Visible = false;
return;
}
var owned = OwnedLevel();
for (var i = 0; i < SkillLevelContainer.GetChildCount(); i++)
SkillLevelContainer.GetChild(i).QueueFree();
for (var i = 0; i < Data.MaxLevel; i++)
{
SkillLevelContainer.AddChild(new TextureRect
{
Texture = i < owned ? UnlockedSkillIcon : LockedSkillIcon,
ExpandMode = TextureRect.ExpandModeEnum.FitWidthProportional,
});
}
if (owned >= Data.MaxLevel)
{
Text = $"{Tr(Data.Name)} (MAX)";
Disabled = true;
return;
}
// Upgrades[n] is the cost to reach level n + 1, so the next level costs Upgrades[owned].
Text = $"{Tr(Data.Name)} ({Data.Upgrades[owned].Cost})";
Disabled = false;
}
}