Skip to content

Commit de5c67c

Browse files
feat(run): campfire rest site — rest or smith (content gap: rest-site)
First slice of the remaining genre content gaps: the StS-family REST SITE. Adds StandardEvents.RestSite offering the two staple options — REST (heal a flat amount) or SMITH (upgrade one player-chosen deck card, up to a max level). Authored purely on existing standard run effects (Heal + UpgradeCards over a player-chosen upgradable-card selector) with no engine privilege, so it's placed as an ordinary EventNode like any other event. Smith is offered even with nothing upgradable (resolves to a clean no-op); an availability gate is a follow-up. Worked tests: rest heals only, smith upgrades the chosen card and doesn't heal, smith is a no-op at max upgrade level. Suite green: Run 332; format clean.
1 parent 968b348 commit de5c67c

2 files changed

Lines changed: 94 additions & 0 deletions

File tree

src/RogueDeck.Run/Nodes/StandardEvents.cs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,25 @@ public static EventScript Rest(int healAmount)
1818
.Build();
1919
}
2020

21+
// A campfire / rest site with the genre's two staple options: REST (heal a flat amount) or SMITH (upgrade one
22+
// deck card the player chooses, up to upgradeMaxLevel). Picking either ends the node. Built entirely on the
23+
// standard run effects (Heal + UpgradeCards over a player-chosen upgradable-card selector) — no engine privilege.
24+
// Smith is offered even with nothing upgradable left; it then resolves to no upgrade (an availability gate is a
25+
// follow-up). Content packs place this as an EventNode carrying the script, like any other event.
26+
public static EventScript RestSite(int healAmount, int upgradeMaxLevel = 1)
27+
{
28+
return new EventScriptBuilder("restSite")
29+
.Situation("restSite", "event.restSite", situation => situation
30+
.Choice("rest", choice => choice
31+
.TextKey("event.restSite.rest")
32+
.Heal(healAmount))
33+
.Choice("smith", choice => choice
34+
.TextKey("event.restSite.smith")
35+
.UpgradeCards(
36+
RunSelectors.DeckCards.Upgradable(upgradeMaxLevel).ChooseByPlayer(1, "smith: upgrade a card"))))
37+
.Build();
38+
}
39+
2140
// A treasure: take a reward bundle (the contents are whatever the author passes).
2241
public static EventScript Treasure(RewardId reward, params IRunEffectRequest[] contents)
2342
{
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
using RogueDeck.Core.Combat;
2+
using RogueDeck.Run;
3+
4+
namespace RogueDeck.Run.Tests;
5+
6+
// The campfire / rest site content event (StandardEvents.RestSite): the genre's two staple options, REST (heal a
7+
// flat amount) or SMITH (upgrade one player-chosen deck card). It is authored purely on the standard run effects
8+
// (Heal + UpgradeCards over a player-chosen upgradable-card selector) — no engine privilege — so these tests drive
9+
// it exactly as a run would: an EventNode carrying the script, resolved with a scripted choice + entity chooser.
10+
public class RestSiteTests
11+
{
12+
private static RunDefinitionRegistry BuildRegistry()
13+
{
14+
var builder = new RunDefinitionRegistryBuilder();
15+
new StandardRunPackage().RegisterDefinitions(builder);
16+
return builder.Build();
17+
}
18+
19+
private static RunState DeckOf(int current, int max, params string[] kinds)
20+
{
21+
var run = new RunState(new RunId("run"), new HealthState(current, max), new RunMap(Array.Empty<Node>()));
22+
foreach (var kind in kinds)
23+
run.AddDeckCard(new CardDefinitionId(kind));
24+
return run;
25+
}
26+
27+
// Resolve the rest-site node once, taking the given choice; the scripted provider doubles as the entity chooser
28+
// (it picks the first candidate) so a smith upgrades the first upgradable card in the deck.
29+
private static void Play(RunState run, EventScript script, string choice)
30+
{
31+
var registry = BuildRegistry();
32+
var processor = new RunEffectProcessor();
33+
var provider = new ScriptedChoiceProvider(choice);
34+
run.SetEntityChooser(provider);
35+
var node = new Node(new NodeId("n"), StandardRunIds.EventNode, script);
36+
new EventNodeResolver().Resolve(new NodeResolveContext(run, provider, registry, processor), node);
37+
processor.ResolvePending(run, registry);
38+
}
39+
40+
[Fact]
41+
public void Rest_heals_the_flat_amount()
42+
{
43+
var run = DeckOf(20, 40, "strike", "defend");
44+
45+
Play(run, StandardEvents.RestSite(healAmount: 12), "rest");
46+
47+
Assert.Equal(32, run.Health.Current); // 20 + 12
48+
Assert.All(run.Deck, c => Assert.Equal(0, c.UpgradeLevel)); // resting upgrades nothing
49+
}
50+
51+
[Fact]
52+
public void Smith_upgrades_the_chosen_deck_card_and_leaves_health_alone()
53+
{
54+
var run = DeckOf(20, 40, "strike", "defend");
55+
56+
Play(run, StandardEvents.RestSite(healAmount: 12), "smith");
57+
58+
var strike = run.Deck.Single(c => c.DefinitionId == new CardDefinitionId("strike"));
59+
Assert.Equal(1, strike.UpgradeLevel); // the chosen (first upgradable) card is smithed
60+
var defend = run.Deck.Single(c => c.DefinitionId == new CardDefinitionId("defend"));
61+
Assert.Equal(0, defend.UpgradeLevel); // the other card is untouched
62+
Assert.Equal(20, run.Health.Current); // smithing does not heal
63+
}
64+
65+
[Fact]
66+
public void Smith_does_not_re_upgrade_past_the_max_level()
67+
{
68+
var run = DeckOf(20, 40, "strike");
69+
run.Deck[0].Upgrade(); // already at the default max (level 1)
70+
71+
Play(run, StandardEvents.RestSite(healAmount: 12, upgradeMaxLevel: 1), "smith");
72+
73+
Assert.Equal(1, run.Deck[0].UpgradeLevel); // no upgradable card ⇒ smith is a clean no-op
74+
}
75+
}

0 commit comments

Comments
 (0)