From 75bd836c624154b7729c0552316eaf889db3acec Mon Sep 17 00:00:00 2001 From: Christian 'ketura' McCarty Date: Tue, 8 Apr 2025 23:21:40 -0500 Subject: [PATCH] Optional effect type overhaul - So it turns out that Optional is frequently used to wrap a CostToEffect, but because of how that's set up the Optional does not ensure the "Cost" part is actually enforced. Thus someone can choose "yes" and it will always treat both halves of a CostToEffect as optional. - Added the standard "requires" and "cost" fields to Optional - Refactored about 30 cards to no longer use CostToEffect nested within an Optional, instead opting to use the cost field of Optional itself --- .../cards/official/set01/set01-Elven.hjson | 23 ++-- .../cards/official/set05/set05-Dunland.hjson | 63 ++++----- .../cards/official/set09/set09-Elven.hjson | 23 ++-- .../cards/official/set09/set09-Gandalf.hjson | 20 +-- .../cards/official/set09/set09-Rohan.hjson | 21 ++- .../cards/official/set10/set10-Elven.hjson | 21 ++- .../cards/official/set11/set11-Gollum.hjson | 13 +- .../cards/official/set11/set11-Men.hjson | 15 +- .../cards/official/set12/set12-Shire.hjson | 17 +-- .../cards/official/set13/set13-Gollum.hjson | 13 +- .../cards/official/set13/set13-Orc.hjson | 19 ++- .../cards/official/set13/set13-Rohan.hjson | 7 +- .../cards/official/set18/set18-Orc.hjson | 45 +++--- .../cards/official/set19/set19-Sauron.hjson | 13 +- .../hobbit/set30/set30-shadow.hjson | 17 +-- .../hobbit/set32/set32-shadow.hjson | 7 +- .../pc/errata/set02/set02-Elven-errata.hjson | 17 +-- .../pc/errata/set09/set09-Elven-errata.hjson | 23 ++-- .../errata/set09/set09-Gandalf-errata.hjson | 21 ++- .../pc/errata/set13/set13-Sites-errata.hjson | 19 ++- .../unofficial/pc/setV01/V1_gandalf.hjson | 31 ++--- .../cards/unofficial/pc/setV02/V2-Sites.hjson | 19 ++- .../field/effect/appender/CostToEffect.java | 10 ++ .../build/field/effect/appender/Optional.java | 40 +++++- .../com/gempukku/lotro/at/ExertAtTest.java | 2 +- .../official/set05/Card_05_001_Tests.java | 22 +-- .../official/set11/Card_11_104_Tests.java | 129 ++++++++++++++++-- .../errata/set18/Card_18_134_ErrataTests.java | 2 +- 28 files changed, 373 insertions(+), 299 deletions(-) diff --git a/gemp-lotr/gemp-lotr-cards/src/main/resources/cards/official/set01/set01-Elven.hjson b/gemp-lotr/gemp-lotr-cards/src/main/resources/cards/official/set01/set01-Elven.hjson index 2d19a9926..dc06eda10 100644 --- a/gemp-lotr/gemp-lotr-cards/src/main/resources/cards/official/set01/set01-Elven.hjson +++ b/gemp-lotr/gemp-lotr-cards/src/main/resources/cards/official/set01/set01-Elven.hjson @@ -688,20 +688,17 @@ } true: { type: optional - text: Would you like to discard an ISENGARD minion revealed to draw 2 cards? + text: Would you like to discard an {ISENGARD} minion revealed to draw 2 cards? + cost: { + type: discardFromHand + hand: shadowPlayer + forced: true + player: you + select: choose(memory(revealedCards),culture(isengard),minion) + } effect: { - type: costToEffect - cost: { - type: discardFromHand - hand: shadowPlayer - forced: true - player: you - select: choose(memory(revealedCards),culture(isengard),minion) - } - effect: { - type: drawCards - count: 2 - } + type: drawCards + count: 2 } } } diff --git a/gemp-lotr/gemp-lotr-cards/src/main/resources/cards/official/set05/set05-Dunland.hjson b/gemp-lotr/gemp-lotr-cards/src/main/resources/cards/official/set05/set05-Dunland.hjson index b21503dcd..5c04be4db 100644 --- a/gemp-lotr/gemp-lotr-cards/src/main/resources/cards/official/set05/set05-Dunland.hjson +++ b/gemp-lotr/gemp-lotr-cards/src/main/resources/cards/official/set05/set05-Dunland.hjson @@ -28,19 +28,16 @@ type: optional player: fp text: Would you like to discard 2 cards from hand to discard Dunlending Rampager? + cost: { + type: discardFromHand + forced: false + player: fp + hand: fp + count: 2 + } effect: { - type: costToEffect - cost: { - type: discardFromHand - forced: false - player: fp - hand: fp - count: 2 - } - effect: { - type: discard - select: self - } + type: discard + select: self } } } @@ -84,19 +81,16 @@ type: optional player: fp text: Would you like to discard 2 cards from hand to discard Dunlending Rampager? + cost: { + type: discardFromHand + forced: false + player: fp + hand: fp + count: 2 + } effect: { - type: costToEffect - cost: { - type: discardFromHand - forced: false - player: fp - hand: fp - count: 2 - } - effect: { - type: discard - select: self - } + type: discard + select: self } } } @@ -204,19 +198,16 @@ type: optional player: fp text: Would you like to discard 4 cards from hand to discard Wild Men of the Hills? + cost: { + type: discardFromHand + forced: false + player: fp + hand: fp + count: 4 + } effect: { - type: costToEffect - cost: { - type: discardFromHand - forced: false - player: fp - hand: fp - count: 4 - } - effect: { - type: discard - select: self - } + type: discard + select: self } } } diff --git a/gemp-lotr/gemp-lotr-cards/src/main/resources/cards/official/set09/set09-Elven.hjson b/gemp-lotr/gemp-lotr-cards/src/main/resources/cards/official/set09/set09-Elven.hjson index 6ebe762a5..8293b5e5a 100644 --- a/gemp-lotr/gemp-lotr-cards/src/main/resources/cards/official/set09/set09-Elven.hjson +++ b/gemp-lotr/gemp-lotr-cards/src/main/resources/cards/official/set09/set09-Elven.hjson @@ -240,20 +240,17 @@ { type: optional text: Would you like to exert Glorfindel to make a Nazgul he is skirmishing strength -X, where X is the twilight cost the the card revealed? + cost: { + type: exert + select: self + } effect: { - type: costToEffect - cost: { - type: exert - select: self - } - effect: { - type: modifyStrength - select: choose(nazgul,inSkirmishAgainst(self)) - amount: { - type: twilightCostInMemory - memory: revealedCard - multiplier: -1 - } + type: modifyStrength + select: choose(nazgul,inSkirmishAgainst(self)) + amount: { + type: twilightCostInMemory + memory: revealedCard + multiplier: -1 } } } diff --git a/gemp-lotr/gemp-lotr-cards/src/main/resources/cards/official/set09/set09-Gandalf.hjson b/gemp-lotr/gemp-lotr-cards/src/main/resources/cards/official/set09/set09-Gandalf.hjson index 727145e47..0d1098343 100644 --- a/gemp-lotr/gemp-lotr-cards/src/main/resources/cards/official/set09/set09-Gandalf.hjson +++ b/gemp-lotr/gemp-lotr-cards/src/main/resources/cards/official/set09/set09-Gandalf.hjson @@ -168,17 +168,17 @@ type: optional player: shadowPlayer text: Would you like to discard 2 cards to draw 2 cards? + cost: { + type: discardFromHand + player: shadow + hand: shadow + forced: false + count: 2 + } effect: { - type: costToEffect - cost: { - type: discardFromHand - forced: false - count: 2 - } - effect: { - type: drawCards - count: 2 - } + type: drawCards + player: shadow + count: 2 } } } diff --git a/gemp-lotr/gemp-lotr-cards/src/main/resources/cards/official/set09/set09-Rohan.hjson b/gemp-lotr/gemp-lotr-cards/src/main/resources/cards/official/set09/set09-Rohan.hjson index b86f4c039..6f6d98837 100644 --- a/gemp-lotr/gemp-lotr-cards/src/main/resources/cards/official/set09/set09-Rohan.hjson +++ b/gemp-lotr/gemp-lotr-cards/src/main/resources/cards/official/set09/set09-Rohan.hjson @@ -80,19 +80,16 @@ } { type: optional - text: Would you like exert the played ROHAN Man to play a possession on him or her? + text: Would you like exert the played {ROHAN} Man to play a possession on him or her? + cost: { + type: exert + select: memory(playedRohanMan) + ignoreCostCheckFailure: true + } effect: { - type: costToEffect - cost: { - type: exert - select: memory(playedRohanMan) - ignoreCostCheckFailure: true - } - effect: { - type: PlayCardFromHand - select: choose(possession) - on: memory(playedRohanMan) - } + type: PlayCardFromHand + select: choose(possession) + on: memory(playedRohanMan) } } { diff --git a/gemp-lotr/gemp-lotr-cards/src/main/resources/cards/official/set10/set10-Elven.hjson b/gemp-lotr/gemp-lotr-cards/src/main/resources/cards/official/set10/set10-Elven.hjson index afcbdea1a..0662faf09 100644 --- a/gemp-lotr/gemp-lotr-cards/src/main/resources/cards/official/set10/set10-Elven.hjson +++ b/gemp-lotr/gemp-lotr-cards/src/main/resources/cards/official/set10/set10-Elven.hjson @@ -199,19 +199,16 @@ } true: { type: optional - text: Discard the card? + text: Discard the card to wound each minion in this skirmish? + cost: { + type: discardCardsFromDrawDeck + select: memory(revealedCard) + showAll: false + shuffle: false + } effect: { - type: costToEffect - cost: { - type: discardCardsFromDrawDeck - select: memory(revealedCard) - showAll: false - shuffle: false - } - effect: { - type: wound - select: all(minion,inSkirmish) - } + type: wound + select: all(minion,inSkirmish) } } } diff --git a/gemp-lotr/gemp-lotr-cards/src/main/resources/cards/official/set11/set11-Gollum.hjson b/gemp-lotr/gemp-lotr-cards/src/main/resources/cards/official/set11/set11-Gollum.hjson index fc900cc2d..186c2456a 100644 --- a/gemp-lotr/gemp-lotr-cards/src/main/resources/cards/official/set11/set11-Gollum.hjson +++ b/gemp-lotr/gemp-lotr-cards/src/main/resources/cards/official/set11/set11-Gollum.hjson @@ -381,15 +381,12 @@ } { type: optional - text: Do you want to return it to hand? + text: Do you want to add a burden to return {self} to hand? + cost: { + type: addBurdens + } effect: { - type: costToEffect - cost: { - type: addBurdens - } - effect: { - type: putPlayedEventIntoHand - } + type: putPlayedEventIntoHand } } ] diff --git a/gemp-lotr/gemp-lotr-cards/src/main/resources/cards/official/set11/set11-Men.hjson b/gemp-lotr/gemp-lotr-cards/src/main/resources/cards/official/set11/set11-Men.hjson index 48ec6d2d4..428f97598 100644 --- a/gemp-lotr/gemp-lotr-cards/src/main/resources/cards/official/set11/set11-Men.hjson +++ b/gemp-lotr/gemp-lotr-cards/src/main/resources/cards/official/set11/set11-Men.hjson @@ -1733,16 +1733,13 @@ true: { type: optional text: Would you like to remove (3) to wound {woundedCharacter} again? + cost: { + type: removeTwilight + amount: 3 + } effect: { - type: costToEffect - cost: { - type: removeTwilight - amount: 3 - } - effect: { - type: wound - select: memory(woundedCharacter) - } + type: wound + select: memory(woundedCharacter) } } } diff --git a/gemp-lotr/gemp-lotr-cards/src/main/resources/cards/official/set12/set12-Shire.hjson b/gemp-lotr/gemp-lotr-cards/src/main/resources/cards/official/set12/set12-Shire.hjson index e4c74b7ca..a1eae1ebf 100644 --- a/gemp-lotr/gemp-lotr-cards/src/main/resources/cards/official/set12/set12-Shire.hjson +++ b/gemp-lotr/gemp-lotr-cards/src/main/resources/cards/official/set12/set12-Shire.hjson @@ -349,17 +349,14 @@ } true: { type: optional - text: Would you like to discard 2 cards to return this event to your hand? + text: Would you like to discard 2 cards to return {self} to your hand? + cost: { + type: discardFromHand + forced: false + count: 2 + } effect: { - type: costToEffect - cost: { - type: discardFromHand - forced: false - count: 2 - } - effect: { - type: putPlayedEventIntoHand - } + type: putPlayedEventIntoHand } } } diff --git a/gemp-lotr/gemp-lotr-cards/src/main/resources/cards/official/set13/set13-Gollum.hjson b/gemp-lotr/gemp-lotr-cards/src/main/resources/cards/official/set13/set13-Gollum.hjson index fcab88a08..85688d038 100644 --- a/gemp-lotr/gemp-lotr-cards/src/main/resources/cards/official/set13/set13-Gollum.hjson +++ b/gemp-lotr/gemp-lotr-cards/src/main/resources/cards/official/set13/set13-Gollum.hjson @@ -856,15 +856,12 @@ effect: { type: optional text: Would you like to heal the Ring-beaer to add a burden? + cost: { + type: heal + select: choose(ringBearer) + } effect: { - type: costToEffect - cost: { - type: heal - select: choose(ringBearer) - } - effect: { - type: addBurdens - } + type: addBurdens } } } diff --git a/gemp-lotr/gemp-lotr-cards/src/main/resources/cards/official/set13/set13-Orc.hjson b/gemp-lotr/gemp-lotr-cards/src/main/resources/cards/official/set13/set13-Orc.hjson index c619ff1c8..0523d7b34 100644 --- a/gemp-lotr/gemp-lotr-cards/src/main/resources/cards/official/set13/set13-Orc.hjson +++ b/gemp-lotr/gemp-lotr-cards/src/main/resources/cards/official/set13/set13-Orc.hjson @@ -963,18 +963,15 @@ type: event cost: { type: optional - text: Would you like to remove 2 burdens? + text: Would you like to remove 2 burdens to add +4 more strength? + cost: { + type: removeBurdens + amount: 2 + } effect: { - type: costToEffect - cost: { - type: removeBurdens - amount: 2 - } - effect: { - type: MemorizeInfo - memory: removedBurdens - info: yes - } + type: MemorizeInfo + memory: removedBurdens + info: yes } } effect: [ diff --git a/gemp-lotr/gemp-lotr-cards/src/main/resources/cards/official/set13/set13-Rohan.hjson b/gemp-lotr/gemp-lotr-cards/src/main/resources/cards/official/set13/set13-Rohan.hjson index 27727af1f..ce864a128 100644 --- a/gemp-lotr/gemp-lotr-cards/src/main/resources/cards/official/set13/set13-Rohan.hjson +++ b/gemp-lotr/gemp-lotr-cards/src/main/resources/cards/official/set13/set13-Rohan.hjson @@ -740,10 +740,8 @@ } true: { type: optional - text: Would you like to exert Théoden? - effect: { - type: costToEffect - cost: { + text: Would you like to exert Théoden to add +3 strength and damage +1? + cost: { type: exert select: choose(name(Théoden)) } @@ -752,7 +750,6 @@ memory: exertedTheoden info: yes } - } } } { diff --git a/gemp-lotr/gemp-lotr-cards/src/main/resources/cards/official/set18/set18-Orc.hjson b/gemp-lotr/gemp-lotr-cards/src/main/resources/cards/official/set18/set18-Orc.hjson index 0e114bc1b..ed982d32f 100644 --- a/gemp-lotr/gemp-lotr-cards/src/main/resources/cards/official/set18/set18-Orc.hjson +++ b/gemp-lotr/gemp-lotr-cards/src/main/resources/cards/official/set18/set18-Orc.hjson @@ -671,31 +671,28 @@ type: optional player: free people text: Would you like to add a burden or exert the Ring-bearer twice to discard Orkish Sneak? + cost: { + type: choice + player: free people + texts: [ + Add a burden + Exert the Ring-bearer twice + ] + effects: [ + { + type: addBurdens + } + { + type: exert + select: choose(ringBearer) + times: 2 + } + ] + } effect: { - type: costToEffect - cost: { - type: choice - player: free people - texts: [ - Add a burden - Exert the Ring-bearer twice - ] - effects: [ - { - type: addBurdens - } - { - type: exert - select: choose(ringBearer) - times: 2 - } - ] - } - effect: { - type: discard - player: free people - select: self - } + type: discard + player: free people + select: self } } ] diff --git a/gemp-lotr/gemp-lotr-cards/src/main/resources/cards/official/set19/set19-Sauron.hjson b/gemp-lotr/gemp-lotr-cards/src/main/resources/cards/official/set19/set19-Sauron.hjson index fe13e5c8a..26db65749 100644 --- a/gemp-lotr/gemp-lotr-cards/src/main/resources/cards/official/set19/set19-Sauron.hjson +++ b/gemp-lotr/gemp-lotr-cards/src/main/resources/cards/official/set19/set19-Sauron.hjson @@ -25,15 +25,12 @@ } { type: optional - text: Would you like to remove (1) to place this event on the bottom of your draw deck? + text: Would you like to remove (1) to place {self} on the bottom of your draw deck? + cost: { + type: removeTwilight + } effect: { - type: costToEffect - cost: { - type: removeTwilight - } - effect: { - type: putPlayedEventOnBottomOfDrawDeck - } + type: putPlayedEventOnBottomOfDrawDeck } } ] diff --git a/gemp-lotr/gemp-lotr-cards/src/main/resources/cards/unofficial/hobbit/set30/set30-shadow.hjson b/gemp-lotr/gemp-lotr-cards/src/main/resources/cards/unofficial/hobbit/set30/set30-shadow.hjson index c657a5e79..7a290d084 100644 --- a/gemp-lotr/gemp-lotr-cards/src/main/resources/cards/unofficial/hobbit/set30/set30-shadow.hjson +++ b/gemp-lotr/gemp-lotr-cards/src/main/resources/cards/unofficial/hobbit/set30/set30-shadow.hjson @@ -281,17 +281,14 @@ { type: optional player: fp - text: Would you like to add a burden to discard this minion? + text: Would you like to add a burden to discard {self}? + cost: { + type: addBurdens + amount: 1 + } effect: { - type: costToEffect - cost: { - type: addBurdens - amount: 1 - } - effect: { - type: discard - select: self - } + type: discard + select: self } } ] diff --git a/gemp-lotr/gemp-lotr-cards/src/main/resources/cards/unofficial/hobbit/set32/set32-shadow.hjson b/gemp-lotr/gemp-lotr-cards/src/main/resources/cards/unofficial/hobbit/set32/set32-shadow.hjson index c5bc679f3..6ad0a7d99 100644 --- a/gemp-lotr/gemp-lotr-cards/src/main/resources/cards/unofficial/hobbit/set32/set32-shadow.hjson +++ b/gemp-lotr/gemp-lotr-cards/src/main/resources/cards/unofficial/hobbit/set32/set32-shadow.hjson @@ -1133,10 +1133,8 @@ } { type: optional - text: Would you like to discard 2 Orcs to take this event back into hand? - effect: { - type: costToEffect - cost: { + text: Would you like to discard 2 Orcs to take {self} back into hand? + cost: { type: discard select: choose(orc) count: 2 @@ -1144,7 +1142,6 @@ effect: { type: putPlayedEventIntoHand } - } } ] } diff --git a/gemp-lotr/gemp-lotr-cards/src/main/resources/cards/unofficial/pc/errata/set02/set02-Elven-errata.hjson b/gemp-lotr/gemp-lotr-cards/src/main/resources/cards/unofficial/pc/errata/set02/set02-Elven-errata.hjson index 2b96a8de4..7cc5bcbb1 100644 --- a/gemp-lotr/gemp-lotr-cards/src/main/resources/cards/unofficial/pc/errata/set02/set02-Elven-errata.hjson +++ b/gemp-lotr/gemp-lotr-cards/src/main/resources/cards/unofficial/pc/errata/set02/set02-Elven-errata.hjson @@ -47,17 +47,14 @@ type: Optional player: you text: Would you like to exert another Elf ally to discard another condition? + cost: { + type: exert + select: choose(Elf,ally,not(memory(firstAlly))) + } effect: { - type: CostToEffect - cost: { - type: exert - select: choose(Elf,ally,not(memory(firstAlly))) - } - effect: { - type: Discard - count: 1 - select: choose(condition) - } + type: Discard + count: 1 + select: choose(condition) } } } diff --git a/gemp-lotr/gemp-lotr-cards/src/main/resources/cards/unofficial/pc/errata/set09/set09-Elven-errata.hjson b/gemp-lotr/gemp-lotr-cards/src/main/resources/cards/unofficial/pc/errata/set09/set09-Elven-errata.hjson index 2fb708b4f..32162516e 100644 --- a/gemp-lotr/gemp-lotr-cards/src/main/resources/cards/unofficial/pc/errata/set09/set09-Elven-errata.hjson +++ b/gemp-lotr/gemp-lotr-cards/src/main/resources/cards/unofficial/pc/errata/set09/set09-Elven-errata.hjson @@ -152,20 +152,17 @@ { type: optional text: Would you like to exert Glorfindel to make a Nazgul he is skirmishing strength -X, where X is the twilight cost the the card revealed? + cost: { + type: exert + select: self + } effect: { - type: costToEffect - cost: { - type: exert - select: self - } - effect: { - type: modifyStrength - select: choose(nazgul,inSkirmishAgainst(self)) - amount: { - type: twilightCostInMemory - memory: revealedCard - multiplier: -1 - } + type: modifyStrength + select: choose(nazgul,inSkirmishAgainst(self)) + amount: { + type: twilightCostInMemory + memory: revealedCard + multiplier: -1 } } } diff --git a/gemp-lotr/gemp-lotr-cards/src/main/resources/cards/unofficial/pc/errata/set09/set09-Gandalf-errata.hjson b/gemp-lotr/gemp-lotr-cards/src/main/resources/cards/unofficial/pc/errata/set09/set09-Gandalf-errata.hjson index fec3c086b..1526772e0 100644 --- a/gemp-lotr/gemp-lotr-cards/src/main/resources/cards/unofficial/pc/errata/set09/set09-Gandalf-errata.hjson +++ b/gemp-lotr/gemp-lotr-cards/src/main/resources/cards/unofficial/pc/errata/set09/set09-Gandalf-errata.hjson @@ -19,7 +19,6 @@ twilight: 4 type: Companion race: Wizard - #keyword: damage+1 strength: 7 vitality: 4 signet: Gandalf @@ -42,17 +41,17 @@ type: optional player: shadowPlayer text: Would you like to discard 2 cards to draw 2 cards? + cost: { + type: discardFromHand + player: shadow + hand: shadow + forced: false + count: 2 + } effect: { - type: costToEffect - cost: { - type: discardFromHand - forced: false - count: 2 - } - effect: { - type: drawCards - count: 2 - } + type: drawCards + player: shadow + count: 2 } } } diff --git a/gemp-lotr/gemp-lotr-cards/src/main/resources/cards/unofficial/pc/errata/set13/set13-Sites-errata.hjson b/gemp-lotr/gemp-lotr-cards/src/main/resources/cards/unofficial/pc/errata/set13/set13-Sites-errata.hjson index 755881a5b..9e9d3949c 100644 --- a/gemp-lotr/gemp-lotr-cards/src/main/resources/cards/unofficial/pc/errata/set13/set13-Sites-errata.hjson +++ b/gemp-lotr/gemp-lotr-cards/src/main/resources/cards/unofficial/pc/errata/set13/set13-Sites-errata.hjson @@ -30,18 +30,15 @@ type: optional player: shadow text: Would you like to discard 2 cards from hand to add a burden? + cost: { + type: discardFromHand + hand: shadowPlayer + player: shadowPlayer + forced: false + count: 2 + } effect: { - type: costToEffect - cost: { - type: discardFromHand - hand: shadowPlayer - player: shadowPlayer - forced: false - count: 2 - } - effect: { - type: addBurdens - } + type: addBurdens } } } diff --git a/gemp-lotr/gemp-lotr-cards/src/main/resources/cards/unofficial/pc/setV01/V1_gandalf.hjson b/gemp-lotr/gemp-lotr-cards/src/main/resources/cards/unofficial/pc/setV01/V1_gandalf.hjson index a17e06232..c7b57f4a8 100644 --- a/gemp-lotr/gemp-lotr-cards/src/main/resources/cards/unofficial/pc/setV01/V1_gandalf.hjson +++ b/gemp-lotr/gemp-lotr-cards/src/main/resources/cards/unofficial/pc/setV01/V1_gandalf.hjson @@ -29,24 +29,21 @@ } effect: { type: optional - text: Would you like to pay this card's twilight cost to take it into hand? + text: Would you like to pay {chosenAlly} twilight cost to take it into hand? + cost: { + type: addtwilight + amount: { + type: twilightCostInMemory + memory: chosenAlly + } + } effect: { - type: costToEffect - cost: { - type: addtwilight - amount: { - type: twilightCostInMemory - memory: chosenAlly - } - } - effect: { - type: putcardsfromdeckintohand - count: 1 - select: memory(chosenAlly) - reveal: true - shuffle: false - showAll: false - } + type: putcardsfromdeckintohand + count: 1 + select: memory(chosenAlly) + reveal: true + shuffle: false + showAll: false } } } diff --git a/gemp-lotr/gemp-lotr-cards/src/main/resources/cards/unofficial/pc/setV02/V2-Sites.hjson b/gemp-lotr/gemp-lotr-cards/src/main/resources/cards/unofficial/pc/setV02/V2-Sites.hjson index e39c221cb..5f5f2df9b 100644 --- a/gemp-lotr/gemp-lotr-cards/src/main/resources/cards/unofficial/pc/setV02/V2-Sites.hjson +++ b/gemp-lotr/gemp-lotr-cards/src/main/resources/cards/unofficial/pc/setV02/V2-Sites.hjson @@ -32,18 +32,15 @@ effect: { type: Optional text: Would you like to discard 2 cards to draw 2? + cost: { + type: DiscardFromHand + forced: false + select: choose(any) + count: 2 + } effect: { - type: CostToEffect - cost: { - type: DiscardFromHand - forced: false - select: choose(any) - count: 2 - } - effect: { - type: DrawCards - count: 2 - } + type: DrawCards + count: 2 } } } diff --git a/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/cards/build/field/effect/appender/CostToEffect.java b/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/cards/build/field/effect/appender/CostToEffect.java index 56b654203..ac3ce7d9c 100644 --- a/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/cards/build/field/effect/appender/CostToEffect.java +++ b/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/cards/build/field/effect/appender/CostToEffect.java @@ -70,6 +70,16 @@ public class CostToEffect implements EffectAppenderProducer { return true; } + + @Override + public boolean isPlayabilityCheckedForEffect() { + //This is done intentinally, even though one would think that you would want to + // check the "cost" part of "CostToEffect". The reason for this is because CostToEffect + // is frequently used as a wrapper in contexts where the Cost cannot actually be checked + // ahead of time, such as when memory values are crucial to the execution (which of course + // are not popuulated before the action itself is executed at time of checking). + return false; + } }; } } diff --git a/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/cards/build/field/effect/appender/Optional.java b/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/cards/build/field/effect/appender/Optional.java index 3ac025d13..b7eb62716 100644 --- a/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/cards/build/field/effect/appender/Optional.java +++ b/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/cards/build/field/effect/appender/Optional.java @@ -17,31 +17,43 @@ import org.json.simple.JSONObject; public class Optional implements EffectAppenderProducer { @Override public EffectAppender createEffectAppender(boolean cost, JSONObject effectObject, CardGenerationEnvironment environment) throws InvalidCardDefinitionException { - FieldUtils.validateAllowedFields(effectObject, "player", "text", "effect"); + FieldUtils.validateAllowedFields(effectObject, "player", "text", "requires", "cost", "effect"); final String player = FieldUtils.getString(effectObject.get("player"), "player", "you"); final String text = FieldUtils.getString(effectObject.get("text"), "text"); + final JSONObject[] costArray = FieldUtils.getObjectArray(effectObject.get("cost"), "cost"); final JSONObject[] effectArray = FieldUtils.getObjectArray(effectObject.get("effect"), "effect"); + final JSONObject[] requiresArray = FieldUtils.getObjectArray(effectObject.get("requires"), "requires"); if (text == null) throw new InvalidCardDefinitionException("There is a text required for optional effects"); final PlayerSource playerSource = PlayerResolver.resolvePlayer(player); + final EffectAppender[] costAppenders = environment.getEffectAppenderFactory().getEffectAppenders(true, costArray, environment); final EffectAppender[] effectAppenders = environment.getEffectAppenderFactory().getEffectAppenders(cost, effectArray, environment); + final Requirement[] requirements = environment.getRequirementFactory().getRequirements(requiresArray, environment); return new DelayedAppender() { @Override protected Effect createEffect(boolean cost, CostToEffectAction action, ActionContext actionContext) { final String choosingPlayer = playerSource.getPlayer(actionContext); + ActionContext delegate = new DelegateActionContext(actionContext, + choosingPlayer, actionContext.getGame(), actionContext.getSource(), + actionContext.getEffectResult(), actionContext.getEffect()); + + if(!isPlayableInFull(actionContext)) + return null; + SubAction subAction = new SubAction(action); + subAction.appendCost( new PlayoutDecisionEffect(choosingPlayer, new YesNoDecision(GameUtils.substituteText(text, actionContext)) { @Override protected void yes() { - ActionContext delegate = new DelegateActionContext(actionContext, - choosingPlayer, actionContext.getGame(), actionContext.getSource(), - actionContext.getEffectResult(), actionContext.getEffect()); + for (EffectAppender costAppender : costAppenders) + costAppender.appendEffect(true, subAction, actionContext); + for (EffectAppender effectAppender : effectAppenders) { effectAppender.appendEffect(cost, subAction, delegate); } @@ -56,8 +68,18 @@ public class Optional implements EffectAppenderProducer { ActionContext delegate = new DelegateActionContext(actionContext, choosingPlayer, actionContext.getGame(), actionContext.getSource(), actionContext.getEffectResult(), actionContext.getEffect()); + + if(!checkRequirements(delegate)) + return false; + + for (EffectAppender costAppender : costAppenders) { + if (!costAppender.isPlayableInFull(delegate)) + return false; + } + for (EffectAppender effectAppender : effectAppenders) { - if (!effectAppender.isPlayableInFull(delegate)) + if (effectAppender.isPlayabilityCheckedForEffect() + && !effectAppender.isPlayableInFull(delegate)) return false; } @@ -72,6 +94,14 @@ public class Optional implements EffectAppenderProducer { } return false; } + + private boolean checkRequirements(ActionContext actionContext) { + for (Requirement req : requirements) { + if (!req.accepts(actionContext)) + return false; + } + return true; + } }; } } diff --git a/gemp-lotr/gemp-lotr-server/src/test/java/com/gempukku/lotro/at/ExertAtTest.java b/gemp-lotr/gemp-lotr-server/src/test/java/com/gempukku/lotro/at/ExertAtTest.java index ce420bb27..04cfe88df 100644 --- a/gemp-lotr/gemp-lotr-server/src/test/java/com/gempukku/lotro/at/ExertAtTest.java +++ b/gemp-lotr/gemp-lotr-server/src/test/java/com/gempukku/lotro/at/ExertAtTest.java @@ -55,7 +55,7 @@ public class ExertAtTest extends AbstractAtTest{ // Pass on viewing revealed card playerDecided(P2, ""); // Can't exert (already exhausted) - playerDecided(P1, "0"); + //playerDecided(P1, "0"); assertEquals(10, _game.getModifiersQuerying().getStrength(_game, cantea)); } } diff --git a/gemp-lotr/gemp-lotr-server/src/test/java/com/gempukku/lotro/cards/official/set05/Card_05_001_Tests.java b/gemp-lotr/gemp-lotr-server/src/test/java/com/gempukku/lotro/cards/official/set05/Card_05_001_Tests.java index f80a74cc2..4ab803ee7 100644 --- a/gemp-lotr/gemp-lotr-server/src/test/java/com/gempukku/lotro/cards/official/set05/Card_05_001_Tests.java +++ b/gemp-lotr/gemp-lotr-server/src/test/java/com/gempukku/lotro/cards/official/set05/Card_05_001_Tests.java @@ -3,7 +3,6 @@ package com.gempukku.lotro.cards.official.set05; import com.gempukku.lotro.cards.GenericCardTestHelper; import com.gempukku.lotro.common.*; import com.gempukku.lotro.game.CardNotFoundException; -import com.gempukku.lotro.game.PhysicalCardImpl; import com.gempukku.lotro.logic.decisions.DecisionResultInvalidException; import org.junit.Test; @@ -18,7 +17,7 @@ public class Card_05_001_Tests return new GenericCardTestHelper( new HashMap<>() {{ - put("card", "5_1"); + put("rampager", "5_1"); // put other cards in here as needed for the test case }}, GenericCardTestHelper.FellowshipSites, @@ -47,7 +46,7 @@ public class Card_05_001_Tests var scn = GetScenario(); - var card = scn.GetFreepsCard("card"); + var card = scn.GetFreepsCard("rampager"); assertEquals("Dunlending Rampager", card.getBlueprint().getTitle()); assertNull(card.getBlueprint().getSubtitle()); @@ -62,18 +61,21 @@ public class Card_05_001_Tests assertEquals(3, card.getBlueprint().getSiteNumber()); } - // Uncomment any @Test markers below once this is ready to be used - //@Test - public void DunlendingRampagerTest1() throws DecisionResultInvalidException, CardNotFoundException { + @Test + public void DunlendingRampagerDoesNotOfferChoiceWhenNotEnoughCardsInFreepsHand() throws DecisionResultInvalidException, CardNotFoundException { //Pre-game setup var scn = GetScenario(); - var card = scn.GetFreepsCard("card"); - scn.FreepsMoveCardToHand(card); + var rampager = scn.GetShadowCard("rampager"); + scn.ShadowMoveCardToHand(rampager); scn.StartGame(); - scn.FreepsPlayCard(card); + scn.FreepsPassCurrentPhaseAction(); + assertTrue(scn.ShadowPlayAvailable(rampager)); + scn.ShadowPlayCard(rampager); - assertEquals(1, scn.GetTwilight()); + assertEquals(Zone.SHADOW_CHARACTERS, rampager.getZone()); + assertEquals(0, scn.GetFreepsHandCount()); + assertFalse(scn.FreepsDecisionAvailable("Would you like to discard 2 cards from hand to discard Dunlending Rampager?")); } } diff --git a/gemp-lotr/gemp-lotr-server/src/test/java/com/gempukku/lotro/cards/official/set11/Card_11_104_Tests.java b/gemp-lotr/gemp-lotr-server/src/test/java/com/gempukku/lotro/cards/official/set11/Card_11_104_Tests.java index 614e098c7..8f4e53c83 100644 --- a/gemp-lotr/gemp-lotr-server/src/test/java/com/gempukku/lotro/cards/official/set11/Card_11_104_Tests.java +++ b/gemp-lotr/gemp-lotr-server/src/test/java/com/gempukku/lotro/cards/official/set11/Card_11_104_Tests.java @@ -1,12 +1,11 @@ package com.gempukku.lotro.cards.official.set11; import com.gempukku.lotro.cards.GenericCardTestHelper; -import com.gempukku.lotro.common.CardType; -import com.gempukku.lotro.common.Culture; -import com.gempukku.lotro.common.Side; -import com.gempukku.lotro.common.Timeword; +import com.gempukku.lotro.common.*; +import com.gempukku.lotro.filters.Filters; import com.gempukku.lotro.game.CardNotFoundException; import com.gempukku.lotro.logic.decisions.DecisionResultInvalidException; +import com.gempukku.lotro.logic.modifiers.AddKeywordModifier; import org.junit.Test; import java.util.HashMap; @@ -20,8 +19,8 @@ public class Card_11_104_Tests return new GenericCardTestHelper( new HashMap<>() {{ - put("card", "11_104"); - // put other cards in here as needed for the test case + put("death", "11_104"); + put("mouth", "12_73"); }}, GenericCardTestHelper.FellowshipSites, GenericCardTestHelper.FOTRFrodo, @@ -41,12 +40,13 @@ public class Card_11_104_Tests * Twilight Cost: 3 * Type: Event * Subtype: Skirmish - * Game Text: Wound a character skirmishing a [men] minion. If the fellowship is at a battleground site, you may remove (3) to wound that character again. + * Game Text: Wound a character skirmishing a [men] minion. + * If the fellowship is at a battleground site, you may remove (3) to wound that character again. */ var scn = GetScenario(); - var card = scn.GetFreepsCard("card"); + var card = scn.GetFreepsCard("death"); assertEquals("Whistling Death", card.getBlueprint().getTitle()); assertNull(card.getBlueprint().getSubtitle()); @@ -58,18 +58,117 @@ public class Card_11_104_Tests assertEquals(3, card.getBlueprint().getTwilightCost()); } - // Uncomment any @Test markers below once this is ready to be used - //@Test - public void WhistlingDeathTest1() throws DecisionResultInvalidException, CardNotFoundException { + @Test + public void WhistlingDeathWoundsThenRemoves3MoreToWoundAgain() throws DecisionResultInvalidException, CardNotFoundException { //Pre-game setup var scn = GetScenario(); - var card = scn.GetFreepsCard("card"); - scn.FreepsMoveCardToHand(card); + var death = scn.GetShadowCard("death"); + var mouth = scn.GetShadowCard("mouth"); + scn.ShadowMoveCharToTable(mouth); + scn.ShadowMoveCardToHand(death); + + var frodo = scn.GetRingBearer(); + + //cheating to ensure site 2 qualifies + scn.ApplyAdHocModifier(new AddKeywordModifier(null, Filters.siteNumber(2), null, Keyword.BATTLEGROUND)); scn.StartGame(); - scn.FreepsPlayCard(card); - assertEquals(3, scn.GetTwilight()); + scn.SetTwilight(7); + scn.FreepsPassCurrentPhaseAction(); + scn.SkipToAssignments(); + scn.FreepsAssignToMinions(frodo, mouth); + scn.FreepsResolveSkirmish(frodo); + scn.FreepsPassCurrentPhaseAction(); + + assertEquals(0, scn.GetWoundsOn(frodo)); + assertEquals(10, scn.GetTwilight()); + assertTrue(scn.hasKeyword(scn.GetCurrentSite(), Keyword.BATTLEGROUND)); + assertTrue(scn.ShadowPlayAvailable(death)); + + scn.ShadowPlayCard(death); + scn.FreepsDeclineOptionalTrigger(); //the one ring + assertEquals(1, scn.GetWoundsOn(frodo)); + assertEquals(7, scn.GetTwilight()); + + assertTrue(scn.ShadowDecisionAvailable("Would you like to remove (3) to wound")); + scn.ShadowChooseYes(); + scn.FreepsDeclineOptionalTrigger(); //the one ring + assertEquals(2, scn.GetWoundsOn(frodo)); + assertEquals(4, scn.GetTwilight()); + } + + @Test + public void WhistlingDeathSecondWoundIsNotOfferedAtNonBattleground() throws DecisionResultInvalidException, CardNotFoundException { + //Pre-game setup + var scn = GetScenario(); + + var death = scn.GetShadowCard("death"); + var mouth = scn.GetShadowCard("mouth"); + scn.ShadowMoveCharToTable(mouth); + scn.ShadowMoveCardToHand(death); + + var frodo = scn.GetRingBearer(); + + scn.StartGame(); + + scn.SetTwilight(7); + scn.FreepsPassCurrentPhaseAction(); + scn.SkipToAssignments(); + scn.FreepsAssignToMinions(frodo, mouth); + scn.FreepsResolveSkirmish(frodo); + scn.FreepsPassCurrentPhaseAction(); + + assertEquals(0, scn.GetWoundsOn(frodo)); + assertEquals(10, scn.GetTwilight()); + assertFalse(scn.hasKeyword(scn.GetCurrentSite(), Keyword.BATTLEGROUND)); + assertTrue(scn.ShadowPlayAvailable(death)); + + scn.ShadowPlayCard(death); + scn.FreepsDeclineOptionalTrigger(); //the one ring + assertEquals(1, scn.GetWoundsOn(frodo)); + assertEquals(7, scn.GetTwilight()); + + assertFalse(scn.ShadowDecisionAvailable("Would you like to remove (3) to wound")); + assertTrue(scn.FreepsAnyDecisionsAvailable()); + } + + @Test + public void WhistlingDeathSecondWoundIsNotOfferedWhenLessThan3TwilightAvailable() throws DecisionResultInvalidException, CardNotFoundException { + //Pre-game setup + var scn = GetScenario(); + + var death = scn.GetShadowCard("death"); + var mouth = scn.GetShadowCard("mouth"); + scn.ShadowMoveCharToTable(mouth); + scn.ShadowMoveCardToHand(death); + + var frodo = scn.GetRingBearer(); + + //cheating to ensure site 2 qualifies + scn.ApplyAdHocModifier(new AddKeywordModifier(null, Filters.siteNumber(2), null, Keyword.BATTLEGROUND)); + + scn.StartGame(); + + scn.SetTwilight(2); + scn.FreepsPassCurrentPhaseAction(); + scn.SkipToAssignments(); + scn.FreepsAssignToMinions(frodo, mouth); + scn.FreepsResolveSkirmish(frodo); + scn.FreepsPassCurrentPhaseAction(); + + assertEquals(0, scn.GetWoundsOn(frodo)); + assertEquals(5, scn.GetTwilight()); + assertTrue(scn.hasKeyword(scn.GetCurrentSite(), Keyword.BATTLEGROUND)); + assertTrue(scn.ShadowPlayAvailable(death)); + + scn.ShadowPlayCard(death); + scn.FreepsDeclineOptionalTrigger(); //the one ring + assertEquals(1, scn.GetWoundsOn(frodo)); + assertEquals(2, scn.GetTwilight()); + + assertFalse(scn.ShadowDecisionAvailable("Would you like to remove (3) to wound")); + assertTrue(scn.FreepsAnyDecisionsAvailable()); } } diff --git a/gemp-lotr/gemp-lotr-server/src/test/java/com/gempukku/lotro/cards/unofficial/pc/errata/set18/Card_18_134_ErrataTests.java b/gemp-lotr/gemp-lotr-server/src/test/java/com/gempukku/lotro/cards/unofficial/pc/errata/set18/Card_18_134_ErrataTests.java index 04e02489e..7bae4a11d 100644 --- a/gemp-lotr/gemp-lotr-server/src/test/java/com/gempukku/lotro/cards/unofficial/pc/errata/set18/Card_18_134_ErrataTests.java +++ b/gemp-lotr/gemp-lotr-server/src/test/java/com/gempukku/lotro/cards/unofficial/pc/errata/set18/Card_18_134_ErrataTests.java @@ -137,7 +137,7 @@ public class Card_18_134_ErrataTests assertTrue(scn.FreepsPlayAvailable(onegoodturn)); scn.FreepsPlayCard(onegoodturn); //This ensures that we have skipped over choosing a site and instead OGT has moved on to its other clause. - assertTrue(scn.FreepsDecisionAvailable("Do you want to return")); + assertTrue(scn.FreepsDecisionAvailable("Do you want to add a burden")); assertNotEquals(scn.GetSite(3), freepsSite3); assertEquals(scn.GetSite(3), doorway);