From 71b061335587247e5b359bc92df9d731f9fdf5c9 Mon Sep 17 00:00:00 2001 From: Oleg Agafonov Date: Sun, 1 Jun 2025 08:54:24 +0400 Subject: [PATCH] tests: added verify check for wrong target tags usage, improved work with tagged targets (related to 8b2a81cb42c337497a32068fc13cee5ad076812b) --- .../src/mage/cards/f/FriendlyRivalry.java | 70 +++++++++---------- .../cards/single/ltr/FriendlyRivalryTest.java | 61 ++++++++++++++++ .../java/mage/verify/VerifyCardDataTest.java | 14 ++++ Mage/src/main/java/mage/target/Targets.java | 12 ++++ 4 files changed, 121 insertions(+), 36 deletions(-) create mode 100644 Mage.Tests/src/test/java/org/mage/test/cards/single/ltr/FriendlyRivalryTest.java diff --git a/Mage.Sets/src/mage/cards/f/FriendlyRivalry.java b/Mage.Sets/src/mage/cards/f/FriendlyRivalry.java index a04058f3c4b..892eed11554 100644 --- a/Mage.Sets/src/mage/cards/f/FriendlyRivalry.java +++ b/Mage.Sets/src/mage/cards/f/FriendlyRivalry.java @@ -1,23 +1,26 @@ package mage.cards.f; -import java.util.UUID; - import mage.abilities.Ability; import mage.abilities.effects.OneShotEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.constants.*; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.SuperType; import mage.filter.StaticFilters; import mage.filter.common.FilterControlledCreaturePermanent; import mage.filter.predicate.other.AnotherTargetPredicate; import mage.game.Game; import mage.game.permanent.Permanent; -import mage.target.Target; import mage.target.common.TargetControlledCreaturePermanent; import mage.target.common.TargetCreaturePermanent; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.UUID; + /** - * * @author Susucr */ public final class FriendlyRivalry extends CardImpl { @@ -31,20 +34,18 @@ public final class FriendlyRivalry extends CardImpl { public FriendlyRivalry(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{R}{G}"); - - // Target creature you control and up to one other target legendary creature you control - // each deal damage equal to their power to target creature you don't control. + // Target creature you control and up to one other target legendary creature you control each deal damage equal to their power to target creature you don't control. this.getSpellAbility().addEffect(new FriendlyRivalryEffect()); TargetControlledCreaturePermanent target1 = new TargetControlledCreaturePermanent(); - this.getSpellAbility().addTarget(target1.setTargetTag(1)); + this.getSpellAbility().addTarget(target1.setTargetTag(1).withChooseHint("to deal damage")); TargetControlledCreaturePermanent target2 = new TargetControlledCreaturePermanent(0, 1, filter2, false); - this.getSpellAbility().addTarget(target2.setTargetTag(2)); + this.getSpellAbility().addTarget(target2.setTargetTag(2).withChooseHint("to deal damage")); TargetCreaturePermanent target3 = new TargetCreaturePermanent(StaticFilters.FILTER_CREATURE_YOU_DONT_CONTROL); - this.getSpellAbility().addTarget(target3); + this.getSpellAbility().addTarget(target3.setTargetTag(3).withChooseHint("to take damage")); } private FriendlyRivalry(final FriendlyRivalry card) { @@ -62,7 +63,7 @@ class FriendlyRivalryEffect extends OneShotEffect { FriendlyRivalryEffect() { super(Outcome.Benefit); staticText = "Target creature you control and up to one other target legendary " + - "creature you control each deal damage equal to their power to target creature you don't control."; + "creature you control each deal damage equal to their power to target creature you don't control."; } private FriendlyRivalryEffect(final FriendlyRivalryEffect effect) { @@ -77,34 +78,31 @@ class FriendlyRivalryEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { int size = source.getTargets().size(); - if (size < 2) { + if (size < 3) { + throw new IllegalArgumentException("Wrong code usage. Lost targets list, must has 3, but found: " + source.getTargets()); + } + + List toDealDamage = new ArrayList<>(); + source.getTargets().getTargetsByTag(1).stream() + .map(game::getPermanent) + .filter(Objects::nonNull) + .forEach(toDealDamage::add); + source.getTargets().getTargetsByTag(2).stream() + .map(game::getPermanent) + .filter(Objects::nonNull) + .forEach(toDealDamage::add); + Permanent toTakeDamage = source.getTargets().getTargetsByTag(3).stream() + .map(game::getPermanent) + .filter(Objects::nonNull) + .findFirst().orElse(null); + if (toDealDamage.isEmpty() || toTakeDamage == null) { return false; } - Target damageTarget1 = source.getTargets().get(0); - Target damageTarget2 = size == 3 ? source.getTargets().get(1) : null; + toDealDamage.forEach(permanent -> { + toTakeDamage.damage(permanent.getPower().getValue(), permanent.getId(), source, game, false, true); + }); - Target destTarget = source.getTargets().get(size-1); - if ((damageTarget1.getTargets().isEmpty() && (damageTarget2 == null || damageTarget2.getTargets().isEmpty())) - || destTarget.getTargets().isEmpty()) { - return false; - } - - Permanent permanentDamage1 = damageTarget1.getTargets().isEmpty() ? null - : game.getPermanent(damageTarget1.getTargets().get(0)); - Permanent permanentDamage2 = damageTarget2 == null || damageTarget2.getTargets().isEmpty() ? null - : game.getPermanent(damageTarget2.getTargets().get(0)); - Permanent permanentDest = game.getPermanent(destTarget.getTargets().get(0)); - if (permanentDest == null){ - return false; - } - - if (permanentDamage1 != null) { - permanentDest.damage(permanentDamage1.getPower().getValue(), permanentDamage1.getId(), source, game, false, true); - } - if (permanentDamage2 != null) { - permanentDest.damage(permanentDamage2.getPower().getValue(), permanentDamage2.getId(), source, game, false, true); - } return true; } } diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/ltr/FriendlyRivalryTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/ltr/FriendlyRivalryTest.java new file mode 100644 index 00000000000..d5bd6eca155 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/ltr/FriendlyRivalryTest.java @@ -0,0 +1,61 @@ +package org.mage.test.cards.single.ltr; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.player.TestPlayer; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * @author JayDi85 + */ +public class FriendlyRivalryTest extends CardTestPlayerBase { + + @Test + public void test_target_both() { + // Target creature you control and up to one other target legendary creature you control each deal damage + // equal to their power to target creature you don't control. + addCard(Zone.HAND, playerA, "Friendly Rivalry"); // {R}{G} + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 1); + addCard(Zone.BATTLEFIELD, playerA, "Forest", 1); + // + addCard(Zone.BATTLEFIELD, playerA, "Grizzly Bears"); // 2/2 + addCard(Zone.BATTLEFIELD, playerA, "Aesi, Tyrant of Gyre Strait"); // legendary, 5/5 + addCard(Zone.BATTLEFIELD, playerB, "Agonasaur Rex"); // 8/8 + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Friendly Rivalry"); + addTarget(playerA, "Grizzly Bears"); + addTarget(playerA, "Aesi, Tyrant of Gyre Strait"); + addTarget(playerA, "Agonasaur Rex"); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertDamageReceived(playerB, "Agonasaur Rex", 2 + 5); + } + + @Test + public void test_target_single() { + // Target creature you control and up to one other target legendary creature you control each deal damage + // equal to their power to target creature you don't control. + addCard(Zone.HAND, playerA, "Friendly Rivalry"); // {R}{G} + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 1); + addCard(Zone.BATTLEFIELD, playerA, "Forest", 1); + // + addCard(Zone.BATTLEFIELD, playerA, "Grizzly Bears"); // 2/2 + addCard(Zone.BATTLEFIELD, playerA, "Aesi, Tyrant of Gyre Strait"); // legendary, 5/5 + addCard(Zone.BATTLEFIELD, playerB, "Agonasaur Rex"); // 8/8 + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Friendly Rivalry"); + addTarget(playerA, "Grizzly Bears"); + addTarget(playerA, TestPlayer.TARGET_SKIP); // skip second target due "up to" + addTarget(playerA, "Agonasaur Rex"); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertDamageReceived(playerB, "Agonasaur Rex", 2); + } +} \ No newline at end of file diff --git a/Mage.Verify/src/test/java/mage/verify/VerifyCardDataTest.java b/Mage.Verify/src/test/java/mage/verify/VerifyCardDataTest.java index d8985f5193d..9fb178911de 100644 --- a/Mage.Verify/src/test/java/mage/verify/VerifyCardDataTest.java +++ b/Mage.Verify/src/test/java/mage/verify/VerifyCardDataTest.java @@ -43,6 +43,7 @@ import mage.game.permanent.token.TokenImpl; import mage.game.permanent.token.custom.CreatureToken; import mage.game.permanent.token.custom.XmageToken; import mage.sets.TherosBeyondDeath; +import mage.target.Target; import mage.target.targetpointer.TargetPointer; import mage.tournament.cubes.CubeFromDeck; import mage.util.CardUtil; @@ -2279,6 +2280,19 @@ public class VerifyCardDataTest { } }); + // special check: target tags must be used for all targets and starts with 1 + card.getAbilities().stream().filter(a -> a.getTargets().size() >= 2).forEach(a -> { + Set tags = a.getTargets().stream().map(Target::getTargetTag).collect(Collectors.toSet()); + if (tags.size() == 1 && tags.stream().findFirst().get().equals(0)) { + // no tags usage + return; + } + if (tags.size() != a.getTargets().size() || (tags.size() > 1 && tags.contains(0))) { + // how-to fix: make sure each target has it's own target tag like 1 and 2 (don't use 0 because it's default) + fail(card, "abilities", "wrong target tags: miss tag in one of the targets, current list: " + tags); + } + }); + // spells have only 1 ability if (card.isInstantOrSorcery()) { return; diff --git a/Mage/src/main/java/mage/target/Targets.java b/Mage/src/main/java/mage/target/Targets.java index 3be408aa038..3ae7326bcdc 100644 --- a/Mage/src/main/java/mage/target/Targets.java +++ b/Mage/src/main/java/mage/target/Targets.java @@ -40,6 +40,18 @@ public class Targets extends ArrayList implements Copyable { return this; } + public Target getByTag(int tag) { + return this.stream().filter(t -> t.getTargetTag() == tag).findFirst().orElse(null); + } + + public List getTargetsByTag(int tag) { + Target target = getByTag(tag); + if (target == null) { + return new ArrayList<>(); + } + return target.getTargets(); + } + public Target getNextUnchosen(Game game) { return getNextUnchosen(game, 0); }