tests: added verify check for wrong target tags usage, improved work with tagged targets (related to 8b2a81cb42)

This commit is contained in:
Oleg Agafonov 2025-06-01 08:54:24 +04:00
parent 3223d99b2a
commit 71b0613355
4 changed files with 121 additions and 36 deletions

View file

@ -1,23 +1,26 @@
package mage.cards.f; package mage.cards.f;
import java.util.UUID;
import mage.abilities.Ability; import mage.abilities.Ability;
import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.OneShotEffect;
import mage.cards.CardImpl; import mage.cards.CardImpl;
import mage.cards.CardSetInfo; 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.StaticFilters;
import mage.filter.common.FilterControlledCreaturePermanent; import mage.filter.common.FilterControlledCreaturePermanent;
import mage.filter.predicate.other.AnotherTargetPredicate; import mage.filter.predicate.other.AnotherTargetPredicate;
import mage.game.Game; import mage.game.Game;
import mage.game.permanent.Permanent; import mage.game.permanent.Permanent;
import mage.target.Target;
import mage.target.common.TargetControlledCreaturePermanent; import mage.target.common.TargetControlledCreaturePermanent;
import mage.target.common.TargetCreaturePermanent; import mage.target.common.TargetCreaturePermanent;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.UUID;
/** /**
*
* @author Susucr * @author Susucr
*/ */
public final class FriendlyRivalry extends CardImpl { public final class FriendlyRivalry extends CardImpl {
@ -31,20 +34,18 @@ public final class FriendlyRivalry extends CardImpl {
public FriendlyRivalry(UUID ownerId, CardSetInfo setInfo) { public FriendlyRivalry(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{R}{G}"); super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{R}{G}");
// Target creature you control and up to one other target legendary creature you 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.
// each deal damage equal to their power to target creature you don't control.
this.getSpellAbility().addEffect(new FriendlyRivalryEffect()); this.getSpellAbility().addEffect(new FriendlyRivalryEffect());
TargetControlledCreaturePermanent target1 = new TargetControlledCreaturePermanent(); 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); 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); 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) { private FriendlyRivalry(final FriendlyRivalry card) {
@ -62,7 +63,7 @@ class FriendlyRivalryEffect extends OneShotEffect {
FriendlyRivalryEffect() { FriendlyRivalryEffect() {
super(Outcome.Benefit); super(Outcome.Benefit);
staticText = "Target creature you control and up to one other target legendary " + 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) { private FriendlyRivalryEffect(final FriendlyRivalryEffect effect) {
@ -77,34 +78,31 @@ class FriendlyRivalryEffect extends OneShotEffect {
@Override @Override
public boolean apply(Game game, Ability source) { public boolean apply(Game game, Ability source) {
int size = source.getTargets().size(); 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<Permanent> 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; return false;
} }
Target damageTarget1 = source.getTargets().get(0); toDealDamage.forEach(permanent -> {
Target damageTarget2 = size == 3 ? source.getTargets().get(1) : null; 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; return true;
} }
} }

View file

@ -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);
}
}

View file

@ -43,6 +43,7 @@ import mage.game.permanent.token.TokenImpl;
import mage.game.permanent.token.custom.CreatureToken; import mage.game.permanent.token.custom.CreatureToken;
import mage.game.permanent.token.custom.XmageToken; import mage.game.permanent.token.custom.XmageToken;
import mage.sets.TherosBeyondDeath; import mage.sets.TherosBeyondDeath;
import mage.target.Target;
import mage.target.targetpointer.TargetPointer; import mage.target.targetpointer.TargetPointer;
import mage.tournament.cubes.CubeFromDeck; import mage.tournament.cubes.CubeFromDeck;
import mage.util.CardUtil; 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<Integer> 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 // spells have only 1 ability
if (card.isInstantOrSorcery()) { if (card.isInstantOrSorcery()) {
return; return;

View file

@ -40,6 +40,18 @@ public class Targets extends ArrayList<Target> implements Copyable<Targets> {
return this; return this;
} }
public Target getByTag(int tag) {
return this.stream().filter(t -> t.getTargetTag() == tag).findFirst().orElse(null);
}
public List<UUID> getTargetsByTag(int tag) {
Target target = getByTag(tag);
if (target == null) {
return new ArrayList<>();
}
return target.getTargets();
}
public Target getNextUnchosen(Game game) { public Target getNextUnchosen(Game game) {
return getNextUnchosen(game, 0); return getNextUnchosen(game, 0);
} }