feat(progression): use cases (slice 6)
ProgressionService holds the loaded Profile, active Playthrough and active Attempt, so "exactly one active Attempt" has a testable home. EnterLevel always announces LevelStarted - the reason ghost recording and splits never worked on level 1 or after a Continue. Refuses unreachable levels. Does not touch campaign position, so a Replay leaves it alone. SkillCatalogue returns null for a skill with no authored upgrades, killing the Upgrades[0].Cost overrun. PurchaseSkill reads the current level from the ledger first, so one Attempt can unlock then upgrade. HandlePlayerDeath now persists - death was free if you quit. SkillLevel.First/Next driven out here by a real caller. 53 tests.
This commit is contained in:
40
progression/SkillCatalogue.cs
Normal file
40
progression/SkillCatalogue.cs
Normal file
@@ -0,0 +1,40 @@
|
||||
namespace Mr.BrickAdventures.Progression;
|
||||
|
||||
/// <summary>
|
||||
/// The authored upgrade table for every Skill: what each level costs to reach. Built from
|
||||
/// <c>SkillData</c> resources by an adapter — same pattern as <see cref="Campaign"/>, so no port is
|
||||
/// needed and a test can construct one directly.
|
||||
/// </summary>
|
||||
public sealed class SkillCatalogue
|
||||
{
|
||||
private readonly Dictionary<SkillId, IReadOnlyList<Coins>> _costs;
|
||||
|
||||
private SkillCatalogue(Dictionary<SkillId, IReadOnlyList<Coins>> costs) => _costs = costs;
|
||||
|
||||
public static SkillCatalogue Empty() => new(new Dictionary<SkillId, IReadOnlyList<Coins>>());
|
||||
|
||||
/// <summary>
|
||||
/// Builds a catalogue. Each Skill's list is the cost to reach level 1, level 2, and so on, so a
|
||||
/// Skill with an empty list cannot be bought at all.
|
||||
/// </summary>
|
||||
public static SkillCatalogue Of(IReadOnlyDictionary<SkillId, IReadOnlyList<Coins>> costsByLevel) =>
|
||||
new(costsByLevel.ToDictionary(e => e.Key, e => e.Value));
|
||||
|
||||
/// <summary>
|
||||
/// The highest level this Skill can reach, or null when it has no authored upgrades and so
|
||||
/// cannot be bought. Returning null is what stops <c>Upgrades[0].Cost</c> from throwing on the
|
||||
/// five Skills that currently have no upgrade entries.
|
||||
/// </summary>
|
||||
public SkillLevel? MaxLevelOf(SkillId skill) =>
|
||||
_costs.TryGetValue(skill, out var costs) && costs.Count > 0
|
||||
? SkillLevel.Of(costs.Count)
|
||||
: null;
|
||||
|
||||
/// <summary>
|
||||
/// What it costs to reach <paramref name="level"/>, or null if this Skill does not go that high.
|
||||
/// </summary>
|
||||
public Coins? CostToReach(SkillId skill, SkillLevel level) =>
|
||||
_costs.TryGetValue(skill, out var costs) && level.Value <= costs.Count
|
||||
? costs[level.Value - 1]
|
||||
: null;
|
||||
}
|
||||
Reference in New Issue
Block a user