From 022fdad2ecbc4ffe5d9bc43fd7802c17bacf2173 Mon Sep 17 00:00:00 2001 From: Christian 'ketura' McCarty Date: Sat, 4 Mar 2023 01:04:05 -0600 Subject: [PATCH] Orc Butchery errata tests --- .../pc/errata/set01/set01-Sauron-errata.hjson | 111 ++++++--- .../effect/ResponseEventEffectProcessor.java | 4 +- .../effect/trigger/AboutToTakeWound.java | 2 +- .../field/effect/trigger/TakesWound.java | 19 +- .../lotro/cards/GenericCardTestHelper.java | 13 +- .../errata/set01/Card_01_265_ErrataTests.java | 234 ++++++++++++++++++ 6 files changed, 341 insertions(+), 42 deletions(-) create mode 100644 gemp-lotr/gemp-lotr-server/src/test/java/com/gempukku/lotro/cards/unofficial/pc/errata/set01/Card_01_265_ErrataTests.java diff --git a/gemp-lotr/gemp-lotr-cards/src/main/resources/cards/unofficial/pc/errata/set01/set01-Sauron-errata.hjson b/gemp-lotr/gemp-lotr-cards/src/main/resources/cards/unofficial/pc/errata/set01/set01-Sauron-errata.hjson index 6636e3d94..b1275cf5b 100644 --- a/gemp-lotr/gemp-lotr-cards/src/main/resources/cards/unofficial/pc/errata/set01/set01-Sauron-errata.hjson +++ b/gemp-lotr/gemp-lotr-cards/src/main/resources/cards/unofficial/pc/errata/set01/set01-Sauron-errata.hjson @@ -379,44 +379,87 @@ twilight: 0 type: event keyword: response - requires: { - type: canSpot - filter: culture(sauron),orc,inSkirmish - } - effects: { - type: responseEvent - trigger: { - type: killed - filter: companion - } - effect: { - type: repeat - amount: { - type: ForEachInDeadPile - filter: any + effects: [ + { + type: responseEvent + trigger: { + type: killed + filter: companion + } + requires: { + type: canSpot + filter: culture(sauron),orc,inSkirmish } effect: { - type: choice - player: freeps - texts: [ - Add a burden - Discard the top 4 cards of your draw deck - ] - effects: [ - { - type: addBurdens - amount: 1 - } - { - type: discardTopCardsFromDeck - deck: freeps - count: 4 - forced: false - } - ] + type: repeat + amount: { + type: ForEachInDeadPile + filter: any + } + effect: { + type: choice + player: freeps + texts: [ + Add a burden + Discard the top 4 cards of your draw deck + ] + effects: [ + { + type: addBurdens + amount: 1 + } + { + type: discardTopCardsFromDeck + deck: freeps + count: 4 + forced: false + } + ] + } + } + }, + + { + type: responseEvent + requires: { + type: cantSpot + filter: culture(sauron),orc,inSkirmish + } + trigger: { + type: TakesWound + filter: companion,zone(dead) + source: culture(sauron),orc + } + effect: { + type: repeat + amount: { + type: ForEachInDeadPile + filter: any + } + effect: { + type: choice + player: freeps + texts: [ + Add a burden + Discard the top 4 cards of your draw deck + ] + effects: [ + { + type: addBurdens + amount: 1 + } + { + type: discardTopCardsFromDeck + deck: freeps + count: 4 + forced: false + } + ] + } } } - } + + ] gametext: If a companion is killed by a [sauron] Orc, for each card in the dead pile the Free Peoples player must choose to add a burden or discard 4 cards from the top of their draw deck. lore: For sport, Orcs hew cruelly at the bodies of those they slay. promotext: "" diff --git a/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/cards/build/field/effect/ResponseEventEffectProcessor.java b/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/cards/build/field/effect/ResponseEventEffectProcessor.java index 616bc4d60..1033fb849 100644 --- a/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/cards/build/field/effect/ResponseEventEffectProcessor.java +++ b/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/cards/build/field/effect/ResponseEventEffectProcessor.java @@ -11,7 +11,7 @@ import org.json.simple.JSONObject; public class ResponseEventEffectProcessor implements EffectProcessor { @Override public void processEffect(JSONObject value, BuiltLotroCardBlueprint blueprint, CardGenerationEnvironment environment) throws InvalidCardDefinitionException { - FieldUtils.validateAllowedFields(value, "trigger", "cost", "effect"); + FieldUtils.validateAllowedFields(value, "trigger", "requires", "cost", "effect"); final JSONObject[] triggerArray = FieldUtils.getObjectArray(value.get("trigger"), "trigger"); @@ -21,7 +21,7 @@ public class ResponseEventEffectProcessor implements EffectProcessor { DefaultActionSource triggerActionSource = new DefaultActionSource(); triggerActionSource.addPlayRequirement(triggerChecker); - EffectUtils.processCostsAndEffects(value, environment, triggerActionSource); + EffectUtils.processRequirementsCostsAndEffects(value, environment, triggerActionSource); if (before) { blueprint.appendOptionalInHandBeforeAction(triggerActionSource); diff --git a/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/cards/build/field/effect/trigger/AboutToTakeWound.java b/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/cards/build/field/effect/trigger/AboutToTakeWound.java index a2933fc82..de916416c 100644 --- a/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/cards/build/field/effect/trigger/AboutToTakeWound.java +++ b/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/cards/build/field/effect/trigger/AboutToTakeWound.java @@ -16,7 +16,7 @@ public class AboutToTakeWound implements TriggerCheckerProducer { FieldUtils.validateAllowedFields(value, "source", "filter"); String source = FieldUtils.getString(value.get("source"), "source", "any"); - String filter = FieldUtils.getString(value.get("filter"), "source"); + String filter = FieldUtils.getString(value.get("filter"), "filter"); final FilterableSource sourceFilter = environment.getFilterFactory().generateFilter(source, environment); final FilterableSource affectedFilter = environment.getFilterFactory().generateFilter(filter, environment); diff --git a/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/cards/build/field/effect/trigger/TakesWound.java b/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/cards/build/field/effect/trigger/TakesWound.java index b89647217..9dd0387a3 100644 --- a/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/cards/build/field/effect/trigger/TakesWound.java +++ b/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/cards/build/field/effect/trigger/TakesWound.java @@ -6,6 +6,7 @@ import com.gempukku.lotro.cards.build.FilterableSource; import com.gempukku.lotro.cards.build.InvalidCardDefinitionException; import com.gempukku.lotro.cards.build.field.FieldUtils; import com.gempukku.lotro.common.Filterable; +import com.gempukku.lotro.filters.Filters; import com.gempukku.lotro.game.PhysicalCard; import com.gempukku.lotro.logic.timing.TriggerConditions; import com.gempukku.lotro.logic.timing.results.WoundResult; @@ -14,12 +15,15 @@ import org.json.simple.JSONObject; public class TakesWound implements TriggerCheckerProducer { @Override public TriggerChecker getTriggerChecker(JSONObject value, CardGenerationEnvironment environment) throws InvalidCardDefinitionException { - FieldUtils.validateAllowedFields(value, "filter", "memorize"); + FieldUtils.validateAllowedFields(value, "filter", "source", "memorize"); + + String source = FieldUtils.getString(value.get("source"), "source", "any"); + String filter = FieldUtils.getString(value.get("filter"), "filter", "any"); - final String filter = FieldUtils.getString(value.get("filter"), "filter", "any"); final String memorize = FieldUtils.getString(value.get("memorize"), "memorize"); - final FilterableSource filterableSource = environment.getFilterFactory().generateFilter(filter, environment); + final FilterableSource sourceFilter = environment.getFilterFactory().generateFilter(source, environment); + final FilterableSource targetFilterable = environment.getFilterFactory().generateFilter(filter, environment); return new TriggerChecker() { @Override @@ -29,8 +33,15 @@ public class TakesWound implements TriggerCheckerProducer { @Override public boolean accepts(ActionContext actionContext) { - final Filterable filterable = filterableSource.getFilterable(actionContext); + final Filterable filterable = targetFilterable.getFilterable(actionContext); + final Filterable sourceFilterable = sourceFilter.getFilterable(actionContext); final boolean result = TriggerConditions.forEachWounded(actionContext.getGame(), actionContext.getEffectResult(), filterable); + if(result && !source.equals("any")) { + var sources = ((WoundResult) actionContext.getEffectResult()).getSources(); + if(sources.stream().noneMatch(x -> Filters.and(sourceFilterable).accepts(actionContext.getGame(), x))) { + return false; + } + } if (result && memorize != null) { final PhysicalCard woundedCard = ((WoundResult) actionContext.getEffectResult()).getWoundedCard(); actionContext.setCardMemory(memorize, woundedCard); diff --git a/gemp-lotr/gemp-lotr-server/src/test/java/com/gempukku/lotro/cards/GenericCardTestHelper.java b/gemp-lotr/gemp-lotr-server/src/test/java/com/gempukku/lotro/cards/GenericCardTestHelper.java index 574d1a823..ed491de4a 100644 --- a/gemp-lotr/gemp-lotr-server/src/test/java/com/gempukku/lotro/cards/GenericCardTestHelper.java +++ b/gemp-lotr/gemp-lotr-server/src/test/java/com/gempukku/lotro/cards/GenericCardTestHelper.java @@ -233,6 +233,16 @@ public class GenericCardTestHelper extends AbstractAtTest { return actions.stream().anyMatch(x -> x.toLowerCase().contains(lowerAction)); } + public Boolean FreepsChoiceAvailable(String choice) { return ChoiceAvailable(P1, choice); } + public Boolean ShadowChoiceAvailable(String choice) { return ChoiceAvailable(P2, choice); } + public Boolean ChoiceAvailable(String player, String choice) { + List actions = GetADParamAsList(player, "results"); + if(actions == null) + return false; + String lowerChoice = choice.toLowerCase(); + return actions.stream().anyMatch(x -> x.toLowerCase().contains(lowerChoice)); + } + public Boolean FreepsAnyActionsAvailable() { return AnyActionsAvailable(P1); } public Boolean ShadowAnyActionsAvailable() { return AnyActionsAvailable(P2); } public Boolean AnyActionsAvailable(String player) { @@ -498,7 +508,7 @@ public class GenericCardTestHelper extends AbstractAtTest { Arrays.stream(cards).forEach(card -> MoveCardToZone(P1, card, Zone.SUPPORT)); } public void ShadowMoveCardToSupportArea(String...names) { - Arrays.stream(names).forEach(name -> ShadowMoveCardToSupportArea(GetFreepsCard(name))); + Arrays.stream(names).forEach(name -> ShadowMoveCardToSupportArea(GetShadowCard(name))); } public void ShadowMoveCardToSupportArea(PhysicalCardImpl...cards) { Arrays.stream(cards).forEach(card -> MoveCardToZone(P2, card, Zone.SUPPORT)); @@ -790,6 +800,7 @@ public class GenericCardTestHelper extends AbstractAtTest { return _game.getModifiersQuerying().getStrength(_game, card); } public int GetVitality(PhysicalCardImpl card) { return _game.getModifiersQuerying().getVitality(_game, card); } + public int GetResistance(PhysicalCardImpl card) { return _game.getModifiersQuerying().getResistance(_game, card); } public int GetMinionSiteNumber(PhysicalCardImpl card) { return _game.getModifiersQuerying().getMinionSiteNumber(_game, card); } public int GetGeneralSiteNumber(PhysicalCardImpl card) { diff --git a/gemp-lotr/gemp-lotr-server/src/test/java/com/gempukku/lotro/cards/unofficial/pc/errata/set01/Card_01_265_ErrataTests.java b/gemp-lotr/gemp-lotr-server/src/test/java/com/gempukku/lotro/cards/unofficial/pc/errata/set01/Card_01_265_ErrataTests.java new file mode 100644 index 000000000..9ffb2bbb8 --- /dev/null +++ b/gemp-lotr/gemp-lotr-server/src/test/java/com/gempukku/lotro/cards/unofficial/pc/errata/set01/Card_01_265_ErrataTests.java @@ -0,0 +1,234 @@ +package com.gempukku.lotro.cards.unofficial.pc.errata.set01; + +import com.gempukku.lotro.cards.GenericCardTestHelper; +import com.gempukku.lotro.common.*; +import com.gempukku.lotro.game.CardNotFoundException; +import com.gempukku.lotro.logic.decisions.DecisionResultInvalidException; +import org.junit.Test; + +import java.util.HashMap; + +import static org.junit.Assert.*; + +public class Card_01_265_ErrataTests +{ + + protected GenericCardTestHelper GetScenario() throws CardNotFoundException, DecisionResultInvalidException { + return new GenericCardTestHelper( + new HashMap<>() + {{ + put("butchery", "51_265"); + put("slayer", "3_93"); + put("scavengers", "1_179"); + + put("sam", "2_114"); + put("sword", "1_299"); + put("guard1", "1_7"); + put("guard2", "1_7"); + }}, + GenericCardTestHelper.FellowshipSites, + GenericCardTestHelper.FOTRFrodo, + GenericCardTestHelper.FOTRRing + ); + } + + @Test + public void OrcButcheryStatsAndKeywordsAreCorrect() throws DecisionResultInvalidException, CardNotFoundException { + + /** + * Set: 1 + * Title: Orc Butchery + * Unique: False + * Side: SHADOW + * Culture: Sauron + * Twilight Cost: 0 + * Type: event + * Subtype: Response + * Game Text: If a companion is killed by a [sauron] Orc, then for each card in the dead pile the Free Peoples + * player must choose to add a burden or discard 4 cards from the top of their draw deck. + */ + + //Pre-game setup + var scn = GetScenario(); + + var butchery = scn.GetFreepsCard("butchery"); + + assertFalse(butchery.getBlueprint().isUnique()); + assertEquals(Side.SHADOW, butchery.getBlueprint().getSide()); + assertEquals(Culture.SAURON, butchery.getBlueprint().getCulture()); + assertEquals(CardType.EVENT, butchery.getBlueprint().getCardType()); + assertTrue(scn.HasKeyword(butchery, Keyword.RESPONSE)); + assertEquals(0, butchery.getBlueprint().getTwilightCost()); + } + + @Test + public void ButcheryIsTriggeredBySkirmishOverwhelmKill() throws DecisionResultInvalidException, CardNotFoundException { + //Pre-game setup + var scn = GetScenario(); + + var butchery = scn.GetShadowCard("butchery"); + var slayer = scn.GetShadowCard("slayer"); + scn.ShadowMoveCardToHand(butchery); + scn.ShadowMoveCharToTable(slayer); + + var sam = scn.GetFreepsCard("sam"); + scn.FreepsMoveCharToTable(sam); + + scn.StartGame(); + scn.SkipToAssignments(); + + scn.FreepsAssignToMinions(sam, slayer); + scn.FreepsResolveSkirmish(sam); + scn.PassCurrentPhaseActions(); + + assertTrue(scn.ShadowHasOptionalTriggerAvailable()); + } + + @Test + public void ButcheryIsTriggeredBySkirmishWoundKill() throws DecisionResultInvalidException, CardNotFoundException { + //Pre-game setup + var scn = GetScenario(); + + var butchery = scn.GetShadowCard("butchery"); + var slayer = scn.GetShadowCard("slayer"); + scn.ShadowMoveCardToHand(butchery); + scn.ShadowMoveCharToTable(slayer); + + var sam = scn.GetFreepsCard("sam"); + scn.FreepsMoveCharToTable(sam); + scn.FreepsAttachCardsTo(sam, "sword"); + + scn.StartGame(); + scn.AddWoundsToChar(sam, 3); + scn.SkipToAssignments(); + + scn.FreepsAssignToMinions(sam, slayer); + scn.FreepsResolveSkirmish(sam); + scn.PassCurrentPhaseActions(); + + assertTrue(scn.ShadowHasOptionalTriggerAvailable()); + } + + @Test + public void ButcheryIsTriggeredByDirectWoundAbility() throws DecisionResultInvalidException, CardNotFoundException { + //Pre-game setup + var scn = GetScenario(); + + var butchery = scn.GetShadowCard("butchery"); + var slayer = scn.GetShadowCard("slayer"); + scn.ShadowMoveCardToHand(butchery); + scn.ShadowMoveCharToTable(slayer); + + var sam = scn.GetFreepsCard("sam"); + scn.FreepsMoveCharToTable(sam); + + scn.StartGame(); + scn.AddWoundsToChar(sam, 3); + scn.SkipToAssignments(); + scn.FreepsDeclineAssignments(); + scn.ShadowDeclineAssignments(); + + assertEquals(Phase.REGROUP, scn.GetCurrentPhase()); + scn.FreepsPassCurrentPhaseAction(); + scn.ShadowUseCardAction(slayer); + + assertTrue(scn.ShadowHasOptionalTriggerAvailable()); + } + + @Test + public void ButcheryForcesBurdenIfLessThan4TopDeckCards() throws DecisionResultInvalidException, CardNotFoundException { + //Pre-game setup + var scn = GetScenario(); + + var butchery = scn.GetShadowCard("butchery"); + var slayer = scn.GetShadowCard("slayer"); + scn.ShadowMoveCardToHand(butchery); + scn.ShadowMoveCharToTable(slayer); + + var sam = scn.GetFreepsCard("sam"); + scn.FreepsMoveCharToTable(sam); + scn.FreepsMoveCardToDiscard("sword", "guard1", "guard2"); + + scn.StartGame(); + scn.SkipToAssignments(); + + scn.FreepsAssignToMinions(sam, slayer); + scn.FreepsResolveSkirmish(sam); + scn.PassCurrentPhaseActions(); + + assertEquals(3, scn.GetFreepsDeckCount()); + assertEquals(1, scn.GetBurdens()); + assertEquals(Zone.HAND, butchery.getZone()); + scn.ShadowAcceptOptionalTrigger(); + assertEquals(2, scn.GetBurdens()); + assertEquals(Zone.DISCARD, butchery.getZone()); + } + + @Test + public void ButcheryOffersChoiceOfBurdenOr4TopDeck() throws DecisionResultInvalidException, CardNotFoundException { + //Pre-game setup + var scn = GetScenario(); + + var butchery = scn.GetShadowCard("butchery"); + var slayer = scn.GetShadowCard("slayer"); + scn.ShadowMoveCardToHand(butchery); + scn.ShadowMoveCharToTable(slayer); + + var sam = scn.GetFreepsCard("sam"); + scn.FreepsMoveCharToTable(sam); + + scn.StartGame(); + scn.SkipToAssignments(); + + scn.FreepsAssignToMinions(sam, slayer); + scn.FreepsResolveSkirmish(sam); + scn.PassCurrentPhaseActions(); + + assertTrue(scn.ShadowHasOptionalTriggerAvailable()); + scn.ShadowAcceptOptionalTrigger(); + assertTrue(scn.FreepsDecisionAvailable("Choose action to perform")); + assertTrue(scn.FreepsChoiceAvailable("Add a burden")); + assertTrue(scn.FreepsChoiceAvailable("Discard the top 4 cards of your draw deck")); + + assertEquals(6, scn.GetFreepsDeckCount()); + scn.FreepsChooseMultipleChoiceOption("Discard"); + assertEquals(2, scn.GetFreepsDeckCount()); + } + + @Test + public void ButcheryForcesChoicePerDeadPileDenizen() throws DecisionResultInvalidException, CardNotFoundException { + //Pre-game setup + var scn = GetScenario(); + + var butchery = scn.GetShadowCard("butchery"); + var slayer = scn.GetShadowCard("slayer"); + scn.ShadowMoveCardToHand(butchery); + scn.ShadowMoveCharToTable(slayer); + + var sam = scn.GetFreepsCard("sam"); + scn.FreepsMoveCharToTable(sam); + scn.FreepsMoveCardToDeadPile("guard1", "guard2"); + + scn.StartGame(); + scn.SkipToAssignments(); + + scn.FreepsAssignToMinions(sam, slayer); + scn.FreepsResolveSkirmish(sam); + scn.PassCurrentPhaseActions(); + + assertEquals(1, scn.GetBurdens()); + assertTrue(scn.ShadowHasOptionalTriggerAvailable()); + scn.ShadowAcceptOptionalTrigger(); + assertTrue(scn.FreepsChoiceAvailable("Add a burden")); + scn.FreepsChooseMultipleChoiceOption("Burden"); + + assertEquals(2, scn.GetBurdens()); + assertTrue(scn.FreepsChoiceAvailable("Add a burden")); + scn.FreepsChooseMultipleChoiceOption("Burden"); + + assertEquals(3, scn.GetBurdens()); + assertTrue(scn.FreepsChoiceAvailable("Add a burden")); + scn.FreepsChooseMultipleChoiceOption("Burden"); + assertEquals(4, scn.GetBurdens()); + } +}