Merge branch 'grand-card-rework' into v2-cards

This commit is contained in:
Christian 'ketura' McCarty
2024-10-16 20:27:23 -05:00
12 changed files with 407 additions and 93 deletions

View File

@@ -201,7 +201,9 @@ var packBlueprints = {
"SHELBI - Aragorn Starter":"/gemp-lotr/images/boosters/shelbi-aragorn.jpg",
"SHELBI - Gandalf Starter":"/gemp-lotr/images/boosters/shelbi-gandalf.jpg",
"SHELBI - Gimli Starter":"/gemp-lotr/images/boosters/shelbi-gimli.jpg",
"SHELBI - Treebeard Starter":"/gemp-lotr/images/boosters/shelbi-treebeard.jpg"
"SHELBI - Treebeard Starter":"/gemp-lotr/images/boosters/shelbi-treebeard.jpg",
"2024 World Championship Invitation":"https://i.lotrtcgpc.net/other/2024-wc-invite.jpg"
};
class Card {

View File

@@ -1023,12 +1023,22 @@
{
type: chooseYesOrNo
player: you
text: Would you like to play a minion from your discard pile?
text: Would you like to play a minion from your discard?
memorize: choice
}
{
type: playCardFromDiscard
select: choose(minion)
type: conditional
requires: [
{
type: MemoryIs
memory: choice
value: yes
}
]
effect: {
type: playCardFromDiscard
select: choose(minion)
}
}
]
}

View File

@@ -1190,12 +1190,114 @@
effect: {
type: playCardFromDiscard
select: choose(tracker)
//This should be updated to "optional"
nocheck: true
}
}
{
type: endPhase
}
]
/*
// A less obnoxious version that short-circuits the loop if the
// player ever chooses 0 cards at the prompt. Unfortunately, it
// turns out that "memoryIs" does not check card memory, so this
// is useless until those are unified.
effect: [
{
type: MemorizeValue
memory: choice
value: default
}
{
type: repeat
amount: 3
effect: {
type: conditional
requires: [
{
type: Not
requires: {
type: MemoryIs
memory: choice
value: ""
}
}
{
type: HasCardInDiscard
filter: playable,tracker
}
]
effect: {
type: playCardFromDiscard
filter: choose(tracker)
memorize: choice
//Forces the choice to allow 0 options
nocheck: true
}
}
}
{
type: endPhase
}
]
*/
/*
// An obnoxious version that prompts the user with a question
// for each individual tracker played
effect: [
{
type: MemorizeValue
memory: choice
value: yes
}
{
type: repeat
amount: 3
effect: {
type: conditional
requires: [
{
type: MemoryIs
memory: choice
value: yes
}
{
type: HasCardInDiscard
filter: playable,tracker
}
]
effect: [
{
type: chooseYesOrNo
player: you
text: Would you like to play a tracker from your discard pile?
memorize: choice
}
{
type: conditional
requires: [
{
type: MemoryIs
memory: choice
value: yes
}
]
effect: {
type: playCardFromDiscard
filter: choose(tracker)
}
}
]
}
}
{
type: endPhase
}
]
*/
}
}
}

View File

@@ -77,8 +77,18 @@
memorize: choice
}
{
type: playCardFromDiscard
select: choose(unique,culture(wraith),minion)
type: conditional
requires: [
{
type: MemoryIs
memory: choice
value: yes
}
]
effect: {
type: playCardFromDiscard
select: choose(unique,culture(wraith),minion)
}
}
]
}

View File

@@ -302,38 +302,39 @@
type: condition
keyword: support area
effects: [
{
type: toPlay
requires: {
type: CanSpot
filter: Elf
count: 2
}
}
{
type: trigger
trigger: {
type: losesInitiative
side: free peoples
}
requires: {
type: perTurnLimit
limit: 1
}
effect: [
{
type: incrementPerTurnLimit
limit: 1
}
{
type: modifyStrength
select: choose(minion)
amount: -3
until: start(regroup)
}
]
}
]
{
type: toPlay
requires: {
type: CanSpot
filter: Elf
count: 2
}
}
{
type: trigger
optional: true
trigger: {
type: losesInitiative
side: free peoples
}
requires: {
type: perTurnLimit
limit: 1
}
effect: [
{
type: incrementPerTurnLimit
limit: 1
}
{
type: modifyStrength
select: choose(minion)
amount: -3
until: start(regroup)
}
]
}
]
gametext: To play, spot 2 Elves. <br>When you lose initiative, you may make a minion strength 3 until the regroup phase (limit once per turn).
lore: "'The uttermost choice is before you: to repent and go to the Havens... or else to abide the Doom of Men.'"
promotext: ""
@@ -344,6 +345,4 @@
}
}
}
}

View File

@@ -1,4 +1,5 @@
{
100_1: {
cardInfo: {
imagePath: errata/LOTR-ENV0E001.0.jpg

View File

@@ -51,7 +51,7 @@
30%1xRandom Ultra Rare Foil
10%1xRandom Masterwork
10%1xRandom Alt Image Promo
10%1xRandom PC Full Art
30%1xRandom PC Full Art
5%1xRandom Tengwar
1%1x(S)Masterwork Selection
1%1x(S)PC Promo Art Selection
@@ -77,13 +77,14 @@
name: Placement Random Chase Card Selector
type: random
items: [
50%1x(S)Alt Image Promo Selection
40%1x(S)Alt Image Promo Selection
40%1xRandom PC Full Art
10%1x(S)FotR - Tengwar
10%1x(S)TTT - Tengwar
10%1x(S)RotK - Tengwar
10%1x(S)SH - Tengwar
30%1x(S)Masterwork Selection
5%1x(S)PC Promo Art Selection
40%1x(S)Masterwork Selection
40%1x(S)PC Promo Art Selection
]
}
{

View File

@@ -134,6 +134,15 @@
]
}
{
name: 2024 World Championship Invitation
type: pack
recursive: false
items: [
1x2024 World Championship Invitation
]
}
{
name: The Nine Riders Collection
type: pack

View File

@@ -993,6 +993,8 @@ public class GenericCardTestHelper extends AbstractAtTest {
public void FreepsDeclineOptionalTrigger() throws DecisionResultInvalidException { playerDecided(P1, ""); }
public void ShadowAcceptOptionalTrigger() throws DecisionResultInvalidException { playerDecided(P2, "0"); }
public void ShadowDeclineOptionalTrigger() throws DecisionResultInvalidException { playerDecided(P2, ""); }
public void FreepsDeclineChoosing() throws DecisionResultInvalidException { playerDecided(P1, ""); }
public void ShadowDeclineChoosing() throws DecisionResultInvalidException { playerDecided(P2, ""); }
public void FreepsDeclineReconciliation() throws DecisionResultInvalidException { FreepsPassCurrentPhaseAction(); }
public void ShadowDeclineReconciliation() throws DecisionResultInvalidException { ShadowPassCurrentPhaseAction(); }

View File

@@ -184,6 +184,40 @@ public class Card_01_234_Tests
assertFalse(scn.ShadowDecisionAvailable("play a minion"));
}
@Test
public void NerteaStopsPlayingMinionsIfUserDeclinesPartwayThrough() throws DecisionResultInvalidException, CardNotFoundException {
//Pre-game setup
GenericCardTestHelper scn = GetScenario();
scn.FreepsMoveCharToTable("comp2", "comp3", "comp4", "comp5", "comp6");
var twk = scn.GetShadowCard("twk");
var attea = scn.GetShadowCard("attea");
var nertea = scn.GetShadowCard("nertea");
scn.ShadowMoveCardToDiscard("runner", "rit", "twk", "attea");
scn.ShadowMoveCardToDiscard(twk);
scn.ShadowMoveCardToHand(nertea);
scn.StartGame();
scn.SetTwilight(30);
scn.FreepsPassCurrentPhaseAction();
scn.ShadowPlayCard(nertea);
assertTrue(scn.ShadowDecisionAvailable("play a minion"));
scn.ShadowChooseYes();
assertEquals(4, scn.GetShadowCardChoiceCount());
assertEquals(Zone.DISCARD, twk.getZone());
scn.ShadowChooseCardBPFromSelection(twk);
assertEquals(Zone.SHADOW_CHARACTERS, twk.getZone());
assertTrue(scn.ShadowDecisionAvailable("play a minion"));
assertEquals(Zone.DISCARD, attea.getZone());
scn.ShadowChooseNo();
assertEquals(Zone.DISCARD, attea.getZone());
assertFalse(scn.ShadowDecisionAvailable("play a minion"));
}
@Test
public void NerteaDoesNotPromptIfNoMinionsInDiscardPile() throws DecisionResultInvalidException, CardNotFoundException {
//Pre-game setup

View File

@@ -5,6 +5,7 @@ import com.gempukku.lotro.cards.GenericCardTestHelper;
import com.gempukku.lotro.common.CardType;
import com.gempukku.lotro.common.Keyword;
import com.gempukku.lotro.common.Phase;
import com.gempukku.lotro.common.Zone;
import com.gempukku.lotro.game.CardNotFoundException;
import com.gempukku.lotro.logic.decisions.DecisionResultInvalidException;
import com.gempukku.lotro.logic.modifiers.MoveLimitModifier;
@@ -20,11 +21,11 @@ public class Card_01_363_Tests
protected GenericCardTestHelper GetScenario() throws CardNotFoundException, DecisionResultInvalidException {
return new GenericCardTestHelper(
new HashMap<>() {{
put("greenleaf", "1_50");
put("moriatroop1", "1_177");
put("moriatroop2", "1_177");
put("moriatroop3", "1_177");
put("tracker1", "1_261");
put("tracker2", "1_262");
put("tracker3", "1_270");
put("shelob", "8_26");
put("hollowing", "3_54");
}},
new HashMap<>() {{
put("site1", "1_319");
@@ -72,60 +73,168 @@ public class Card_01_363_Tests
//Pre-game setup
var scn = GetScenario();
var moriatroop1 = scn.GetShadowCard("moriatroop1");
var moriatroop2 = scn.GetShadowCard("moriatroop2");
var moriatroop3 = scn.GetShadowCard("moriatroop3");
var shelob = scn.GetShadowCard("shelob");
scn.ShadowMoveCardToHand(moriatroop1, moriatroop2, moriatroop3, shelob);
//Max out the move limit so we don't have to juggle play back and forth
scn.ApplyAdHocModifier(new MoveLimitModifier(null, 10));
var tolbrandir = scn.GetShadowSite(9);
var tracker1 = scn.GetShadowCard("tracker1");
var tracker2 = scn.GetShadowCard("tracker2");
var tracker3 = scn.GetShadowCard("tracker3");
var hollowing = scn.GetShadowCard("hollowing");
scn.ShadowMoveCardToSupportArea(hollowing);
scn.ShadowMoveCardToDiscard(tracker1, tracker2, tracker3);
scn.ShadowMoveCardToDiscard("shelob");
scn.StartGame();
// 1 -> 3
scn.SkipToPhase(Phase.REGROUP);
scn.PassCurrentPhaseActions();
scn.ShadowDeclineReconciliation();
scn.FreepsChooseToMove();
scn.SkipToSite(8);
scn.FreepsPassCurrentPhaseAction();
// 3 -> 4
scn.SkipToPhase(Phase.REGROUP);
scn.PassCurrentPhaseActions();
scn.ShadowDeclineReconciliation();
scn.FreepsChooseToMove();
assertSame(tolbrandir, scn.GetCurrentSite());
assertTrue(scn.ShadowActionAvailable(tolbrandir));
assertTrue(scn.ShadowActionAvailable(hollowing));
assertEquals(Zone.DISCARD, tracker1.getZone());
assertEquals(Zone.DISCARD, tracker2.getZone());
assertEquals(Zone.DISCARD, tracker3.getZone());
assertEquals(10, scn.GetTwilight());
// 4 -> 5
scn.SkipToPhase(Phase.REGROUP);
scn.PassCurrentPhaseActions();
scn.ShadowDeclineReconciliation();
scn.FreepsChooseToMove();
scn.ShadowUseCardAction(tolbrandir);
assertTrue(scn.ShadowDecisionAvailable("Choose card from discard"));
//Should not have the non-tracker in the choice pool
assertEquals(3, scn.ShadowGetBPChoices().size());
// 5 -> 6
scn.SkipToPhase(Phase.REGROUP);
scn.PassCurrentPhaseActions();
scn.ShadowDeclineReconciliation();
scn.FreepsChooseToMove();
scn.ShadowChooseCardBPFromSelection(tracker1);
assertEquals(Zone.SHADOW_CHARACTERS, tracker1.getZone());
assertEquals(9, scn.GetTwilight());
assertTrue(scn.ShadowDecisionAvailable("Choose card from discard"));
// 6 -> 7
scn.SkipToPhase(Phase.REGROUP);
scn.PassCurrentPhaseActions();
scn.ShadowDeclineReconciliation();
scn.FreepsChooseToMove();
scn.ShadowChooseCardBPFromSelection(tracker2);
assertEquals(Zone.SHADOW_CHARACTERS, tracker2.getZone());
assertEquals(7, scn.GetTwilight());
assertTrue(scn.ShadowDecisionAvailable("Choose card from discard"));
// 7 -> 8
scn.SkipToPhase(Phase.REGROUP);
scn.PassCurrentPhaseActions();
scn.ShadowDeclineReconciliation();
scn.FreepsChooseToMove();
scn.ShadowChooseCardBPFromSelection(tracker3);
assertEquals(Zone.SHADOW_CHARACTERS, tracker3.getZone());
assertEquals(4, scn.GetTwilight());
// 8 -> 9
scn.SkipToPhase(Phase.REGROUP);
scn.PassCurrentPhaseActions();
scn.ShadowDeclineReconciliation();
scn.FreepsChooseToMove();
//No more shadow actions should be permitted (but Shadow is still required to pass manually)
assertFalse(scn.ShadowActionAvailable(hollowing));
}
//TODO: actually finish this
@Test
public void TolBrandirActionCanPlay0TrackersAndDecline3() throws DecisionResultInvalidException, CardNotFoundException {
//Pre-game setup
GenericCardTestHelper scn = GetScenario();
var tolbrandir = scn.GetShadowSite(9);
var tracker1 = scn.GetShadowCard("tracker1");
var tracker2 = scn.GetShadowCard("tracker2");
var tracker3 = scn.GetShadowCard("tracker3");
scn.ShadowMoveCardToDiscard(tracker1, tracker2, tracker3);
scn.StartGame();
scn.SkipToSite(8);
scn.FreepsPassCurrentPhaseAction();
assertSame(tolbrandir, scn.GetCurrentSite());
assertTrue(scn.ShadowActionAvailable(tolbrandir));
assertEquals(Zone.DISCARD, tracker1.getZone());
assertEquals(Zone.DISCARD, tracker2.getZone());
assertEquals(Zone.DISCARD, tracker3.getZone());
assertEquals(10, scn.GetTwilight());
scn.ShadowUseCardAction(tolbrandir);
assertTrue(scn.ShadowDecisionAvailable("Choose card from discard"));
//Should not have the non-tracker in the choice pool
assertEquals(3, scn.ShadowGetBPChoices().size());
scn.ShadowDeclineChoosing();
scn.ShadowDeclineChoosing();
scn.ShadowDeclineChoosing();
assertEquals(Zone.DISCARD, tracker1.getZone());
assertEquals(Zone.DISCARD, tracker2.getZone());
assertEquals(Zone.DISCARD, tracker3.getZone());
assertTrue(scn.ShadowDecisionAvailable("Play Shadow action or pass"));
}
@Test
public void TolBrandirActionCanPlay1TrackerAndDecline2() throws DecisionResultInvalidException, CardNotFoundException {
//Pre-game setup
GenericCardTestHelper scn = GetScenario();
var tolbrandir = scn.GetShadowSite(9);
var tracker1 = scn.GetShadowCard("tracker1");
var tracker2 = scn.GetShadowCard("tracker2");
var tracker3 = scn.GetShadowCard("tracker3");
scn.ShadowMoveCardToDiscard(tracker1, tracker2, tracker3);
scn.StartGame();
scn.SkipToSite(8);
scn.FreepsPassCurrentPhaseAction();
assertSame(tolbrandir, scn.GetCurrentSite());
assertTrue(scn.ShadowActionAvailable(tolbrandir));
assertEquals(Zone.DISCARD, tracker1.getZone());
assertEquals(Zone.DISCARD, tracker2.getZone());
assertEquals(Zone.DISCARD, tracker3.getZone());
assertEquals(10, scn.GetTwilight());
scn.ShadowUseCardAction(tolbrandir);
assertTrue(scn.ShadowDecisionAvailable("Choose card from discard"));
//Should not have the non-tracker in the choice pool
assertEquals(3, scn.ShadowGetBPChoices().size());
scn.ShadowChooseCardBPFromSelection(tracker1);
assertEquals(Zone.SHADOW_CHARACTERS, tracker1.getZone());
assertEquals(9, scn.GetTwilight());
assertTrue(scn.ShadowDecisionAvailable("Choose card from discard"));
scn.ShadowDeclineChoosing();
scn.ShadowDeclineChoosing();
assertEquals(Zone.DISCARD, tracker2.getZone());
assertEquals(Zone.DISCARD, tracker3.getZone());
assertTrue(scn.ShadowDecisionAvailable("Play Shadow action or pass"));
}
@Test
public void TolBrandirActionCanPlay2TrackersAndDecline1() throws DecisionResultInvalidException, CardNotFoundException {
//Pre-game setup
GenericCardTestHelper scn = GetScenario();
var tolbrandir = scn.GetShadowSite(9);
var tracker1 = scn.GetShadowCard("tracker1");
var tracker2 = scn.GetShadowCard("tracker2");
var tracker3 = scn.GetShadowCard("tracker3");
scn.ShadowMoveCardToDiscard(tracker1, tracker2, tracker3);
scn.StartGame();
scn.SkipToSite(8);
scn.FreepsPassCurrentPhaseAction();
assertSame(tolbrandir, scn.GetCurrentSite());
assertTrue(scn.ShadowActionAvailable(tolbrandir));
assertEquals(Zone.DISCARD, tracker1.getZone());
assertEquals(Zone.DISCARD, tracker2.getZone());
assertEquals(Zone.DISCARD, tracker3.getZone());
assertEquals(10, scn.GetTwilight());
scn.ShadowUseCardAction(tolbrandir);
assertTrue(scn.ShadowDecisionAvailable("Choose card from discard"));
//Should not have the non-tracker in the choice pool
assertEquals(3, scn.ShadowGetBPChoices().size());
scn.ShadowChooseCardBPFromSelection(tracker1);
assertEquals(Zone.SHADOW_CHARACTERS, tracker1.getZone());
assertEquals(9, scn.GetTwilight());
assertTrue(scn.ShadowDecisionAvailable("Choose card from discard"));
scn.ShadowChooseCardBPFromSelection(tracker2);
assertEquals(Zone.SHADOW_CHARACTERS, tracker2.getZone());
assertEquals(7, scn.GetTwilight());
assertTrue(scn.ShadowDecisionAvailable("Choose card from discard"));
scn.ShadowDeclineChoosing();
assertEquals(Zone.DISCARD, tracker3.getZone());
assertTrue(scn.ShadowDecisionAvailable("Play Shadow action or pass"));
}
}

View File

@@ -180,6 +180,41 @@ public class Card_01_234_ErrataTests
assertFalse(scn.ShadowDecisionAvailable("play a unique WRAITH minion"));
}
@Test
public void NerteaStopsPlayingMinionsIfUserDeclinesPartwayThrough() throws DecisionResultInvalidException, CardNotFoundException {
//Pre-game setup
GenericCardTestHelper scn = GetScenario();
scn.FreepsMoveCharToTable("comp2", "comp3", "comp4", "comp5", "comp6");
var twk = scn.GetShadowCard("twk");
var attea = scn.GetShadowCard("attea");
var nertea = scn.GetShadowCard("nertea");
scn.ShadowMoveCardToDiscard("runner", "rit", "twk", "attea");
scn.ShadowMoveCardToDiscard(twk);
scn.ShadowMoveCardToHand(nertea);
scn.StartGame();
scn.SetTwilight(30);
scn.FreepsPassCurrentPhaseAction();
scn.ShadowPlayCard(nertea);
assertTrue(scn.ShadowDecisionAvailable("play a unique WRAITH minion"));
scn.ShadowChooseYes();
//twk and attea, but not rit or runner
assertEquals(2, scn.GetShadowCardChoiceCount());
assertEquals(Zone.DISCARD, twk.getZone());
scn.ShadowChooseCardBPFromSelection(twk);
assertEquals(Zone.SHADOW_CHARACTERS, twk.getZone());
assertTrue(scn.ShadowDecisionAvailable("play a unique WRAITH minion"));
assertEquals(Zone.DISCARD, attea.getZone());
scn.ShadowChooseNo();
assertEquals(Zone.DISCARD, attea.getZone());
assertFalse(scn.ShadowDecisionAvailable("play a unique WRAITH minion"));
}
@Test
public void NerteaDoesNotPromptIfNoUniqueRingwraithMinionsInDiscardPile() throws DecisionResultInvalidException, CardNotFoundException {
//Pre-game setup
@@ -202,7 +237,7 @@ public class Card_01_234_ErrataTests
//Imported from the at tests
@Test
public void ulaireNerteaCantPlayMinionsOnGreatRiver() throws Exception {
public void UlaireNerteaCantPlayMinionsOnGreatRiver() throws Exception {
var scn = GetScenario();
var _game = scn._game;