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.
176 lines
5.6 KiB
C#
176 lines
5.6 KiB
C#
using System.Collections.Generic;
|
|
using Godot;
|
|
using Godot.Collections;
|
|
using Mr.BrickAdventures.Autoloads;
|
|
using Mr.BrickAdventures.scripts.components;
|
|
using Mr.BrickAdventures.Progression;
|
|
using Mr.BrickAdventures.scripts.Resources;
|
|
|
|
namespace Mr.BrickAdventures.scripts.UI;
|
|
|
|
public partial class Marketplace : Control
|
|
{
|
|
[Export] public Array<SkillData> Skills { get; set; } = [];
|
|
[Export] public GridContainer ToUnlockGrid { get; set; }
|
|
[Export] public GridContainer UnlockedGrid { get; set; }
|
|
[Export] public Font Font { get; set; }
|
|
[Export] public SkillUnlockerComponent SkillUnlockerComponent { get; set; }
|
|
[Export] public Array<Node> ComponentsToDisable { get; set; } = [];
|
|
[Export] public PackedScene MarketplaceButtonScene { get; set; }
|
|
[Export] public PackedScene SkillButtonScene { get; set; }
|
|
|
|
private static ProgressionRoot Progression => ProgressionRoot.Instance;
|
|
private SkillManager SkillManager => SkillManager.Instance;
|
|
private readonly List<Button> _unlockButtons = [];
|
|
private readonly List<SkillButton> _skillButtons = [];
|
|
|
|
public override void _Ready()
|
|
{
|
|
Skills = SkillManager.AvailableSkills;
|
|
|
|
var skillsToUnlock = new List<SkillData>();
|
|
|
|
foreach (var skill in Skills) skillsToUnlock.Add(skill);
|
|
|
|
foreach (var skill in skillsToUnlock) CreateUpgradeButton(skill);
|
|
|
|
foreach (var skill in Skills)
|
|
if (skill != null && Progression?.IsOwned(SkillId.Of(skill.Name)) == true)
|
|
CreateSkillButton(skill);
|
|
|
|
// One announcement covers pickups, purchases and upgrades alike.
|
|
EventBus.Instance.SkillSetChanged += OnSkillSetChanged;
|
|
|
|
if (InputDeviceManager.Instance != null)
|
|
InputDeviceManager.Instance.DeviceChanged += OnDeviceChanged;
|
|
}
|
|
|
|
public override void _ExitTree()
|
|
{
|
|
if (EventBus.Instance != null)
|
|
EventBus.Instance.SkillSetChanged -= OnSkillSetChanged;
|
|
|
|
if (InputDeviceManager.Instance != null)
|
|
InputDeviceManager.Instance.DeviceChanged -= OnDeviceChanged;
|
|
}
|
|
|
|
public override void _Input(InputEvent @event)
|
|
{
|
|
if (@event.IsActionReleased("ui_cancel") && IsVisible())
|
|
{
|
|
Hide();
|
|
foreach (var c in ComponentsToDisable) c.ProcessMode = ProcessModeEnum.Inherit;
|
|
GetViewport().SetInputAsHandled();
|
|
return;
|
|
}
|
|
|
|
if (!@event.IsActionPressed("show_marketplace")) return;
|
|
|
|
if (IsVisible())
|
|
{
|
|
Hide();
|
|
foreach (var c in ComponentsToDisable) c.ProcessMode = ProcessModeEnum.Inherit;
|
|
}
|
|
else
|
|
{
|
|
Show();
|
|
foreach (var c in ComponentsToDisable) c.ProcessMode = ProcessModeEnum.Disabled;
|
|
if (InputDeviceManager.Instance?.IsPointerless == true)
|
|
GrabFirstFocus();
|
|
}
|
|
}
|
|
|
|
private void OnDeviceChanged(int device)
|
|
{
|
|
var d = (InputDeviceManager.InputDevice)device;
|
|
if (d != InputDeviceManager.InputDevice.Mouse && IsVisible())
|
|
GrabFirstFocus();
|
|
}
|
|
|
|
private void GrabFirstFocus()
|
|
{
|
|
foreach (var btn in _unlockButtons)
|
|
{
|
|
if (btn.Visible && !btn.Disabled)
|
|
{
|
|
btn.GrabFocus();
|
|
return;
|
|
}
|
|
}
|
|
foreach (var btn in _skillButtons)
|
|
{
|
|
if (btn.Visible)
|
|
{
|
|
btn.GrabFocus();
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Rebuilds the owned-skill row from what the Playthrough owns, rather than trying to patch it per
|
|
/// event. Cheap at this size, and it cannot drift out of step with the truth.
|
|
/// </summary>
|
|
private void OnSkillSetChanged()
|
|
{
|
|
foreach (var skill in Skills)
|
|
{
|
|
if (skill == null || Progression?.IsOwned(SkillId.Of(skill.Name)) != true) continue;
|
|
if (_skillButtons.Exists(b => b.Data == skill)) continue;
|
|
|
|
CreateSkillButton(skill);
|
|
}
|
|
|
|
RefreshActivationState();
|
|
}
|
|
|
|
private void RefreshActivationState()
|
|
{
|
|
foreach (var btn in _skillButtons)
|
|
{
|
|
if (SkillManager.IsSkillActive(btn.Data)) btn.Activate();
|
|
else btn.Deactivate();
|
|
}
|
|
}
|
|
|
|
private void CreateSkillButton(SkillData skill)
|
|
{
|
|
var button = SkillButtonScene.Instantiate<SkillButton>();
|
|
button.Data = skill;
|
|
button.Setup();
|
|
button.Pressed += () => OnSkillButtonPressed(button);
|
|
button.Activate();
|
|
|
|
_skillButtons.Add(button);
|
|
UnlockedGrid.AddChild(button);
|
|
UnlockedGrid.QueueSort();
|
|
}
|
|
|
|
private void CreateUpgradeButton(SkillData skill)
|
|
{
|
|
var button = MarketplaceButtonScene.Instantiate<MarketplaceButton>();
|
|
button.Data = skill;
|
|
button.Icon = skill.Icon;
|
|
button.Pressed += () => OnUpgradeButtonPressed(skill);
|
|
|
|
_unlockButtons.Add(button);
|
|
ToUnlockGrid.AddChild(button);
|
|
ToUnlockGrid.QueueSort();
|
|
}
|
|
|
|
/// <summary>
|
|
/// One call covers unlocking and upgrading — the Playthrough works out which. At the top level the
|
|
/// button instead toggles the skill on or off, which is a display concern rather than a purchase.
|
|
/// </summary>
|
|
private void OnUpgradeButtonPressed(SkillData skill)
|
|
{
|
|
if (SkillUnlockerComponent.TryBuy(skill) == PurchaseOutcome.AlreadyAtTopLevel)
|
|
SkillManager.ToggleSkillActivation(skill);
|
|
}
|
|
|
|
private void OnSkillButtonPressed(SkillButton button)
|
|
{
|
|
SkillManager.ToggleSkillActivation(button.Data);
|
|
RefreshActivationState();
|
|
}
|
|
} |