From 5bd3ec8dff80baa876e410ab896209c1fa6d4cf5 Mon Sep 17 00:00:00 2001 From: Christian 'ketura' McCarty Date: Thu, 20 Mar 2025 18:07:32 -0500 Subject: [PATCH] Fixed V2 Sirannon Dam ability not replacing a tentacle with the Watcher properly. --- .../cards/unofficial/pc/setV01/V1_moria.hjson | 4 +- .../effect/appender/ReplaceInSkirmish.java | 10 +- .../gempukku/lotro/game/state/GameState.java | 240 ++++++++++-------- .../effects/ReplaceInSkirmishEffect.java | 10 +- .../pc/vsets/set_v01/Card_V1_065_Tests.java | 88 ++++++- 5 files changed, 227 insertions(+), 125 deletions(-) diff --git a/gemp-lotr/gemp-lotr-cards/src/main/resources/cards/unofficial/pc/setV01/V1_moria.hjson b/gemp-lotr/gemp-lotr-cards/src/main/resources/cards/unofficial/pc/setV01/V1_moria.hjson index e2d7eb110..904981eb0 100644 --- a/gemp-lotr/gemp-lotr-cards/src/main/resources/cards/unofficial/pc/setV01/V1_moria.hjson +++ b/gemp-lotr/gemp-lotr-cards/src/main/resources/cards/unofficial/pc/setV01/V1_moria.hjson @@ -640,8 +640,8 @@ effect: [ { type: replaceInSkirmish - filter: tentacle - with: choose(name(Water in the Water)) + filter: tentacle,InSkirmishAgainst(unbound,companion) + with: choose(name(Watcher in the Water)) } ] } diff --git a/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/cards/build/field/effect/appender/ReplaceInSkirmish.java b/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/cards/build/field/effect/appender/ReplaceInSkirmish.java index 90b061bf8..a109dcb46 100644 --- a/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/cards/build/field/effect/appender/ReplaceInSkirmish.java +++ b/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/cards/build/field/effect/appender/ReplaceInSkirmish.java @@ -8,6 +8,7 @@ import com.gempukku.lotro.cards.build.field.FieldUtils; import com.gempukku.lotro.cards.build.field.effect.EffectAppender; import com.gempukku.lotro.cards.build.field.effect.EffectAppenderProducer; import com.gempukku.lotro.cards.build.field.effect.appender.resolver.CardResolver; +import com.gempukku.lotro.filters.Filters; import com.gempukku.lotro.game.PhysicalCard; import com.gempukku.lotro.logic.actions.CostToEffectAction; import com.gempukku.lotro.logic.effects.ReplaceInSkirmishEffect; @@ -23,6 +24,12 @@ public class ReplaceInSkirmish implements EffectAppenderProducer { final String filter = FieldUtils.getString(effectObject.get("filter"), "filter"); final String with = FieldUtils.getString(effectObject.get("with"), "with"); + if (filter == null) + throw new InvalidCardDefinitionException("'filter' is required for ReplaceInSkirmish effect."); + + if (with == null) + throw new InvalidCardDefinitionException("'with' is required for ReplaceInSkirmish effect."); + final FilterableSource filterableSource = environment.getFilterFactory().generateFilter(filter, environment); MultiEffectAppender result = new MultiEffectAppender(); @@ -34,7 +41,8 @@ public class ReplaceInSkirmish implements EffectAppenderProducer { protected Effect createEffect(boolean cost, CostToEffectAction action, ActionContext actionContext) { final PhysicalCard card = actionContext.getCardFromMemory("_temp"); if (card != null) - return new ReplaceInSkirmishEffect(card, filterableSource.getFilterable(actionContext)); + return new ReplaceInSkirmishEffect(card, filterableSource.getFilterable(actionContext), + Filters.inSkirmish); else return new DoNothingEffect(); } diff --git a/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/game/state/GameState.java b/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/game/state/GameState.java index fd1415dec..52702a1be 100644 --- a/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/game/state/GameState.java +++ b/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/game/state/GameState.java @@ -435,42 +435,6 @@ public class GameState { return _inPlay; } - public void removeFromSkirmish(PhysicalCard card) { - removeFromSkirmish(card, true); - } - - public void replaceInSkirmish(PhysicalCard card) { - _skirmish.setFellowshipCharacter(card); - for (GameStateListener gameStateListener : getAllGameStateListeners()) { - gameStateListener.finishSkirmish(); - gameStateListener.startSkirmish(_skirmish.getFellowshipCharacter(), _skirmish.getShadowCharacters()); - } - } - - public void replaceInSkirmishMinion(PhysicalCard card, PhysicalCard removeMinion) { - removeFromSkirmish(removeMinion); - _skirmish.getShadowCharacters().add(card); - for (GameStateListener listener : getAllGameStateListeners()) - listener.addToSkirmish(card); - } - - private void removeFromSkirmish(PhysicalCard card, boolean notify) { - if (_skirmish != null) { - if (_skirmish.getFellowshipCharacter() == card) { - _skirmish.setFellowshipCharacter(null); - _skirmish.addRemovedFromSkirmish(card); - if (notify) - for (GameStateListener listener : getAllGameStateListeners()) - listener.removeFromSkirmish(card); - } - if (_skirmish.getShadowCharacters().remove(card)) { - _skirmish.addRemovedFromSkirmish(card); - if (notify) - for (GameStateListener listener : getAllGameStateListeners()) - listener.removeFromSkirmish(card); - } - } - } public void removeCardsFromZone(String playerPerforming, Collection cards) { for (PhysicalCard card : cards) { @@ -500,16 +464,8 @@ public class GameState { if (zone == Zone.STACKED) ((PhysicalCardImpl) card).stackOn(null); - for (Assignment assignment : new LinkedList<>(_assignments)) { - if (assignment.getFellowshipCharacter() == card) - removeAssignment(assignment); - if (assignment.getShadowCharacters().remove(card)) - if (assignment.getShadowCharacters().isEmpty()) - removeAssignment(assignment); - } - - if (_skirmish != null) - removeFromSkirmish(card, false); + removeFromAssignment(card); + removeFromSkirmish(card, false); removeAllTokens(card); //If this is reset, then there is no way for self-discounting effects (which are evaluated while in the void) @@ -593,6 +549,135 @@ public class GameState { } } + public void replaceInSkirmish(PhysicalCard card) { + _skirmish.setFellowshipCharacter(card); + for (GameStateListener gameStateListener : getAllGameStateListeners()) { + gameStateListener.finishSkirmish(); + gameStateListener.startSkirmish(_skirmish.getFellowshipCharacter(), _skirmish.getShadowCharacters()); + } + } + + public void replaceInSkirmishMinion(PhysicalCard card, PhysicalCard removeMinion) { + removeFromSkirmish(removeMinion); + + if(_skirmish.getShadowCharacters().contains(card)) { + removeFromSkirmish(card); + } + _skirmish.getShadowCharacters().add(card); + for (GameStateListener listener : getAllGameStateListeners()) + listener.addToSkirmish(card); + } + + public void removeFromSkirmish(PhysicalCard card) { + removeFromSkirmish(card, true); + } + private void removeFromSkirmish(PhysicalCard card, boolean notify) { + if (_skirmish == null) + return; + + if (_skirmish.getFellowshipCharacter() == card) { + _skirmish.setFellowshipCharacter(null); + _skirmish.addRemovedFromSkirmish(card); + if (notify) + for (GameStateListener listener : getAllGameStateListeners()) + listener.removeFromSkirmish(card); + } + if (_skirmish.getShadowCharacters().remove(card)) { + _skirmish.addRemovedFromSkirmish(card); + if (notify) + for (GameStateListener listener : getAllGameStateListeners()) + listener.removeFromSkirmish(card); + } + } + + public void removeFromAssignment(PhysicalCard card) { + if( _assignments.isEmpty()) + return; + + for (var assignment : new LinkedList<>(_assignments)) { + if (assignment.getFellowshipCharacter() == card) { + removeAssignment(assignment); + } + if (assignment.getShadowCharacters().remove(card)) { + if (assignment.getShadowCharacters().isEmpty()) { + removeAssignment(assignment); + } + else { + refreshAssignment(assignment); + } + } + } + } + + public void assignToSkirmishes(PhysicalCard fp, Set minions) { + removeFromSkirmish(fp); + for (PhysicalCard minion : minions) { + removeFromSkirmish(minion); + + for (Assignment assignment : new LinkedList<>(_assignments)) { + if (assignment.getShadowCharacters().remove(minion)) + if (assignment.getShadowCharacters().isEmpty()) + removeAssignment(assignment); + } + } + + Assignment assignment = findFreepsAssignment(fp); + if (assignment != null) + assignment.getShadowCharacters().addAll(minions); + else + _assignments.add(new Assignment(fp, new HashSet<>(minions))); + + for (GameStateListener listener : getAllGameStateListeners()) + listener.addAssignment(fp, minions); + } + + public void refreshAssignment(Assignment assignment) { + removeAssignment(assignment); + assignToSkirmishes(assignment.getFellowshipCharacter(), assignment.getShadowCharacters()); + } + + public void removeAssignment(Assignment assignment) { + _assignments.remove(assignment); + for (GameStateListener listener : getAllGameStateListeners()) { + listener.removeAssignment(assignment.getFellowshipCharacter()); + } + } + + public List getAssignments() { + return _assignments; + } + + private Assignment findFreepsAssignment(PhysicalCard fp) { + for (Assignment assignment : _assignments) + if (assignment.getFellowshipCharacter() == fp) + return assignment; + return null; + } + + public void startSkirmish(PhysicalCard fellowshipCharacter, Set shadowCharacters) { + _skirmish = new Skirmish(fellowshipCharacter, new HashSet<>(shadowCharacters)); + for (GameStateListener listener : getAllGameStateListeners()) + listener.startSkirmish(_skirmish.getFellowshipCharacter(), _skirmish.getShadowCharacters()); + } + + public void restartSkirmish(Skirmish skirmish) { + _skirmish = skirmish; + for (GameStateListener listener : getAllGameStateListeners()) + listener.startSkirmish(_skirmish.getFellowshipCharacter(), _skirmish.getShadowCharacters()); + } + + public Skirmish getSkirmish() { + return _skirmish; + } + + public void finishSkirmish() { + if (_skirmish != null) { + _skirmish = null; + for (GameStateListener listener : getAllGameStateListeners()) + listener.finishSkirmish(); + } + } + public void shuffleCardsIntoDeck(Collection cards, String playerId) { List zoneCards = _decks.get(playerId); @@ -1028,69 +1113,6 @@ public class GameState { setTwilight(_twilightPool - Math.min(Math.max(0, twilight), _twilightPool)); } - public void assignToSkirmishes(PhysicalCard fp, Set minions) { - removeFromSkirmish(fp); - for (PhysicalCard minion : minions) { - removeFromSkirmish(minion); - - for (Assignment assignment : new LinkedList<>(_assignments)) { - if (assignment.getShadowCharacters().remove(minion)) - if (assignment.getShadowCharacters().size() == 0) - removeAssignment(assignment); - } - } - - Assignment assignment = findAssignment(fp); - if (assignment != null) - assignment.getShadowCharacters().addAll(minions); - else - _assignments.add(new Assignment(fp, new HashSet<>(minions))); - - for (GameStateListener listener : getAllGameStateListeners()) - listener.addAssignment(fp, minions); - } - - public void removeAssignment(Assignment assignment) { - _assignments.remove(assignment); - for (GameStateListener listener : getAllGameStateListeners()) - listener.removeAssignment(assignment.getFellowshipCharacter()); - } - - public List getAssignments() { - return _assignments; - } - - private Assignment findAssignment(PhysicalCard fp) { - for (Assignment assignment : _assignments) - if (assignment.getFellowshipCharacter() == fp) - return assignment; - return null; - } - - public void startSkirmish(PhysicalCard fellowshipCharacter, Set shadowCharacters) { - _skirmish = new Skirmish(fellowshipCharacter, new HashSet<>(shadowCharacters)); - for (GameStateListener listener : getAllGameStateListeners()) - listener.startSkirmish(_skirmish.getFellowshipCharacter(), _skirmish.getShadowCharacters()); - } - - public void restartSkirmish(Skirmish skirmish) { - _skirmish = skirmish; - for (GameStateListener listener : getAllGameStateListeners()) - listener.startSkirmish(_skirmish.getFellowshipCharacter(), _skirmish.getShadowCharacters()); - } - - public Skirmish getSkirmish() { - return _skirmish; - } - - public void finishSkirmish() { - if (_skirmish != null) { - _skirmish = null; - for (GameStateListener listener : getAllGameStateListeners()) - listener.finishSkirmish(); - } - } - public PhysicalCard removeTopDeckCard(String player) { List deck = _decks.get(player); if (deck.size() > 0) { diff --git a/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/logic/effects/ReplaceInSkirmishEffect.java b/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/logic/effects/ReplaceInSkirmishEffect.java index 49c6608e1..7def39c1d 100644 --- a/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/logic/effects/ReplaceInSkirmishEffect.java +++ b/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/logic/effects/ReplaceInSkirmishEffect.java @@ -1,6 +1,7 @@ package com.gempukku.lotro.logic.effects; import com.gempukku.lotro.common.Filterable; +import com.gempukku.lotro.common.Side; import com.gempukku.lotro.filters.Filters; import com.gempukku.lotro.game.PhysicalCard; import com.gempukku.lotro.game.state.LotroGame; @@ -33,7 +34,14 @@ public class ReplaceInSkirmishEffect extends AbstractEffect { @Override protected FullEffectResult playEffectReturningResult(LotroGame game) { if (isPlayableInFull(game)) { - game.getGameState().replaceInSkirmish(_replacedBy); + if(_replacedBy.getBlueprint().getSide() == Side.FREE_PEOPLE) { + game.getGameState().replaceInSkirmish(_replacedBy); + } + else if(_replacedBy.getBlueprint().getSide() == Side.SHADOW) { + var replacing = Filters.findFirstActive(game, _replacingFilter); + game.getGameState().replaceInSkirmishMinion(_replacedBy, replacing); + } + return new FullEffectResult(true); } return new FullEffectResult(false); diff --git a/gemp-lotr/gemp-lotr-server/src/test/java/com/gempukku/lotro/cards/unofficial/pc/vsets/set_v01/Card_V1_065_Tests.java b/gemp-lotr/gemp-lotr-server/src/test/java/com/gempukku/lotro/cards/unofficial/pc/vsets/set_v01/Card_V1_065_Tests.java index d2ef2504e..b5a55fed1 100644 --- a/gemp-lotr/gemp-lotr-server/src/test/java/com/gempukku/lotro/cards/unofficial/pc/vsets/set_v01/Card_V1_065_Tests.java +++ b/gemp-lotr/gemp-lotr-server/src/test/java/com/gempukku/lotro/cards/unofficial/pc/vsets/set_v01/Card_V1_065_Tests.java @@ -1,9 +1,11 @@ package com.gempukku.lotro.cards.unofficial.pc.vsets.set_v01; import com.gempukku.lotro.cards.GenericCardTestHelper; -import com.gempukku.lotro.common.*; +import com.gempukku.lotro.common.CardType; +import com.gempukku.lotro.common.Culture; +import com.gempukku.lotro.common.Keyword; +import com.gempukku.lotro.common.Side; import com.gempukku.lotro.game.CardNotFoundException; -import com.gempukku.lotro.game.PhysicalCardImpl; import com.gempukku.lotro.logic.decisions.DecisionResultInvalidException; import org.junit.Test; @@ -18,8 +20,12 @@ public class Card_V1_065_Tests return new GenericCardTestHelper( new HashMap<>() {{ - put("card", "101_65"); - // put other cards in here as needed for the test case + put("dam", "101_65"); + put("foul", "2_58"); + put("watcher", "2_73"); + + put("aragorn", "1_89"); + }}, GenericCardTestHelper.FellowshipSites, GenericCardTestHelper.FOTRFrodo, @@ -46,7 +52,7 @@ public class Card_V1_065_Tests var scn = GetScenario(); - var card = scn.GetFreepsCard("card"); + var card = scn.GetFreepsCard("dam"); assertEquals("Sirannon Dam", card.getBlueprint().getTitle()); assertNull(card.getBlueprint().getSubtitle()); @@ -58,18 +64,76 @@ public class Card_V1_065_Tests assertEquals(0, card.getBlueprint().getTwilightCost()); } - // Uncomment any @Test markers below once this is ready to be used - //@Test - public void SirannonDamTest1() throws DecisionResultInvalidException, CardNotFoundException { + @Test + public void SirannonDamMakesCompanionsDefenderPlus1() throws DecisionResultInvalidException, CardNotFoundException { //Pre-game setup var scn = GetScenario(); - var card = scn.GetFreepsCard("card"); - scn.FreepsMoveCardToHand(card); + var frodo = scn.GetRingBearer(); + + var dam = scn.GetShadowCard("dam"); + scn.ShadowMoveCardToSupportArea(dam); scn.StartGame(); - scn.FreepsPlayCard(card); + assertTrue(scn.hasKeyword(frodo, Keyword.DEFENDER)); + } - assertEquals(0, scn.GetTwilight()); + @Test + public void SirannonDamMakesSitesWithNoKeywordGainMarsh() throws DecisionResultInvalidException, CardNotFoundException { + //Pre-game setup + var scn = GetScenario(); + + var frodo = scn.GetRingBearer(); + + var dam = scn.GetShadowCard("dam"); + scn.ShadowMoveCardToHand(dam); + + scn.StartGame(); + scn.FreepsPassCurrentPhaseAction(); + + assertFalse(scn.hasKeyword(scn.GetCurrentSite(), Keyword.MARSH)); + scn.ShadowPlayCard(dam); + assertTrue(scn.hasKeyword(scn.GetCurrentSite(), Keyword.MARSH)); + } + + @Test + public void SirannonDamCanRemove2BurdensToMakeWatcherReplaceTentacleInSkirmish() throws DecisionResultInvalidException, CardNotFoundException { + //Pre-game setup + var scn = GetScenario(); + + var frodo = scn.GetRingBearer(); + var aragorn = scn.GetFreepsCard("aragorn"); + scn.FreepsMoveCharToTable(aragorn); + + var dam = scn.GetShadowCard("dam"); + var foul = scn.GetShadowCard("foul"); + var watcher = scn.GetShadowCard("watcher"); + scn.ShadowMoveCardToSupportArea(dam); + scn.ShadowMoveCardToHand(foul, watcher); + + scn.StartGame(); + scn.AddBurdens(2); + scn.SetTwilight(10); + scn.FreepsPassCurrentPhaseAction(); + + scn.ShadowPlayCard(foul); + scn.ShadowDeclineOptionalTrigger(); + scn.ShadowPlayCard(watcher); + + scn.SkipToAssignments(); + scn.FreepsAssignToMinions(aragorn, foul); + scn.ShadowDeclineAssignments(); + scn.FreepsResolveSkirmish(aragorn); + scn.FreepsPassCurrentPhaseAction(); + + assertEquals(3, scn.GetBurdens()); + assertTrue(scn.IsCharSkirmishing(foul)); + assertFalse(scn.IsCharSkirmishing(watcher)); + assertTrue(scn.ShadowActionAvailable(dam)); + + scn.ShadowUseCardAction(dam); + assertEquals(1, scn.GetBurdens()); + assertTrue(scn.IsCharSkirmishing(watcher)); + assertFalse(scn.IsCharSkirmishing(foul)); } }