Reworking goad effects (ready for review) (#8034)

* changing goad to designation, refactored goad effects to be continuous

* [AFC] Implemented Vengeful Ancestor

* reworked effects which goad an attached creature

* updated goading implementation

* updated combat with new goad logic

* some more changes, added a test

* another fix

* update to test, still fails

* added more failing tests

* more failing tests

* added additional goad check

* small fix to two tests (still failing

* added a regular combat test (passes and fails randomly)

* fixed bug in computer player random selection

* some changes to how TargetDefender is handled

* removed unnecessary class

* more combat fixes, tests pass now

* removed tests which no longer work due to combat changes

* small merge fix

* [NEC] Implemented Komainu Battle Armor

* [NEC] Implemented Kaima, the Fractured Calm

* [NEC] added all variants
This commit is contained in:
Evan Kranzler 2022-02-15 09:18:21 -05:00 committed by GitHub
parent 5725873aeb
commit 4591ac07cc
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
30 changed files with 812 additions and 438 deletions

View file

@ -976,24 +976,8 @@ public class ComputerPlayer extends PlayerImpl implements Player {
}
if (target.getOriginalTarget() instanceof TargetDefender) {
// TODO: Improve, now planeswalker is always chosen if it exits
List<Permanent> targets;
targets = game.getBattlefield().getActivePermanents(StaticFilters.FILTER_PERMANENT_PLANESWALKER, randomOpponentId, game);
if (targets != null && !targets.isEmpty()) {
for (Permanent planeswalker : targets) {
if (target.canTarget(abilityControllerId, planeswalker.getId(), source, game)) {
target.addTarget(planeswalker.getId(), source, game);
}
if (target.isChosen()) {
return true;
}
}
}
if (!target.isChosen()) {
if (target.canTarget(abilityControllerId, randomOpponentId, source, game)) {
target.addTarget(randomOpponentId, source, game);
}
}
UUID randomDefender = RandomUtil.randomFromCollection(possibleTargets);
target.addTarget(randomDefender, source, game);
return target.isChosen();
}
@ -2997,21 +2981,7 @@ public class ComputerPlayer extends PlayerImpl implements Player {
* @return
*/
private UUID getRandomOpponent(UUID abilityControllerId, Game game) {
UUID randomOpponentId = null;
Set<UUID> opponents = game.getOpponents(abilityControllerId);
if (opponents.size() > 1) {
int rand = RandomUtil.nextInt(opponents.size());
int count = 0;
for (UUID currentId : opponents) {
if (count == rand) {
randomOpponentId = currentId;
break;
}
}
} else if (opponents.size() == 1) {
randomOpponentId = game.getOpponents(abilityControllerId).iterator().next();
}
return randomOpponentId;
return RandomUtil.randomFromCollection(game.getOpponents(abilityControllerId));
}
@Override

View file

@ -1737,7 +1737,6 @@ public class HumanPlayer extends PlayerImpl {
return true;
} else {
TargetDefender target = new TargetDefender(possibleDefender, attackerId);
target.setNotTarget(true); // player or planswalker hexproof does not prevent attacking a player
if (forcedToAttack) {
StringBuilder sb = new StringBuilder(target.getTargetName());
Permanent attacker = game.getPermanent(attackerId);
@ -1757,7 +1756,6 @@ public class HumanPlayer extends PlayerImpl {
protected UUID selectDefenderForAllAttack(Set<UUID> defenders, Game game) {
TargetDefender target = new TargetDefender(defenders, null);
target.setNotTarget(true); // player or planswalker hexproof does not prevent attacking a player
if (chooseTarget(Outcome.Damage, target, null, game)) {
return getFixedResponseUUID(game);
}

View file

@ -91,7 +91,7 @@ class AgitatorAntEffect extends OneShotEffect {
if (permanent == null || !permanent.addCounters(CounterType.P1P1.createInstance(2), player.getId(), source, game)) {
continue;
}
new GoadTargetEffect().setTargetPointer(new FixedTarget(permanent, game)).apply(game, source);
game.addEffect(new GoadTargetEffect().setTargetPointer(new FixedTarget(permanent, game)), source);
}
return true;
}

View file

@ -1,9 +1,6 @@
package mage.cards.b;
import java.util.UUID;
import mage.abilities.Ability;
import mage.abilities.effects.ContinuousEffect;
import mage.abilities.effects.Effect;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.common.UntapTargetEffect;
import mage.abilities.effects.common.combat.GoadTargetEffect;
@ -20,8 +17,9 @@ import mage.target.common.TargetCreaturePermanent;
import mage.target.targetpointer.FixedTarget;
import mage.target.targetpointer.TargetPointer;
import java.util.UUID;
/**
*
* @author TheElk801
*/
public final class Besmirch extends CardImpl {
@ -66,24 +64,19 @@ class BesmirchEffect extends OneShotEffect {
TargetPointer target = new FixedTarget(source.getFirstTarget(), game);
// gain control
ContinuousEffect effect = new GainControlTargetEffect(Duration.EndOfTurn);
effect.setTargetPointer(target);
game.addEffect(effect, source);
game.addEffect(new GainControlTargetEffect(Duration.EndOfTurn)
.setTargetPointer(target), source);
// haste
effect = new GainAbilityTargetEffect(HasteAbility.getInstance(), Duration.EndOfTurn);
effect.setTargetPointer(target);
game.addEffect(effect, source);
game.addEffect(new GainAbilityTargetEffect(
HasteAbility.getInstance(), Duration.EndOfTurn
).setTargetPointer(target), source);
// goad
Effect effect2 = new GoadTargetEffect();
effect2.setTargetPointer(target);
effect2.apply(game, source);
game.addEffect(new GoadTargetEffect().setTargetPointer(target), source);
// untap
effect2 = new UntapTargetEffect();
effect2.setTargetPointer(target);
effect2.apply(game, source);
new UntapTargetEffect().setTargetPointer(target).apply(game, source);
return true;
}

View file

@ -2,9 +2,10 @@ package mage.cards.b;
import mage.abilities.Ability;
import mage.abilities.common.ActivateAsSorceryActivatedAbility;
import mage.abilities.common.GoadAttachedAbility;
import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.costs.mana.GenericManaCost;
import mage.abilities.effects.common.AttachEffect;
import mage.abilities.effects.common.combat.GoadAttachedEffect;
import mage.abilities.effects.common.continuous.BoostEquippedEffect;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
@ -27,10 +28,12 @@ public final class BloodthirstyBlade extends CardImpl {
this.subtype.add(SubType.EQUIPMENT);
// Equipped creature gets +2/+0 and is goaded.
this.addAbility(new GoadAttachedAbility(new BoostEquippedEffect(2, 0)));
Ability ability = new SimpleStaticAbility(new BoostEquippedEffect(2, 0));
ability.addEffect(new GoadAttachedEffect());
this.addAbility(ability);
// {1}: Attach Bloodthirsty Blade to target creature an opponent controls. Active this ability only any time you could cast a sorcery.
Ability ability = new ActivateAsSorceryActivatedAbility(
ability = new ActivateAsSorceryActivatedAbility(
Zone.BATTLEFIELD,
new AttachEffect(
Outcome.Detriment, "Attach {this} to target creature an opponent controls"

View file

@ -74,7 +74,7 @@ class GeodeRagerEffect extends OneShotEffect {
if (permanent == null) {
continue;
}
new GoadTargetEffect().setTargetPointer(new FixedTarget(permanent, game)).apply(game, source);
game.addEffect(new GoadTargetEffect().setTargetPointer(new FixedTarget(permanent, game)), source);
}
return true;
}

View file

@ -0,0 +1,94 @@
package mage.cards.k;
import mage.MageInt;
import mage.abilities.Ability;
import mage.abilities.common.BeginningOfUpkeepTriggeredAbility;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.common.combat.GoadTargetEffect;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.*;
import mage.counters.CounterType;
import mage.filter.StaticFilters;
import mage.game.Game;
import mage.game.permanent.Permanent;
import mage.target.targetpointer.FixedTarget;
import java.util.Objects;
import java.util.UUID;
/**
* @author TheElk801
*/
public final class KaimaTheFracturedCalm extends CardImpl {
public KaimaTheFracturedCalm(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{R}{G}");
this.addSuperType(SuperType.LEGENDARY);
this.subtype.add(SubType.SPIRIT);
this.power = new MageInt(3);
this.toughness = new MageInt(3);
// At the beginning of your end step, goad each creature your opponents control that's enchanted by an Aura you control. Put a +1/+1 counter on Kaima, the Fractured Calm for each creature goaded this way.
this.addAbility(new BeginningOfUpkeepTriggeredAbility(
new KaimaTheFracturedCalmEffect(), TargetController.YOU, false
));
}
private KaimaTheFracturedCalm(final KaimaTheFracturedCalm card) {
super(card);
}
@Override
public KaimaTheFracturedCalm copy() {
return new KaimaTheFracturedCalm(this);
}
}
class KaimaTheFracturedCalmEffect extends OneShotEffect {
KaimaTheFracturedCalmEffect() {
super(Outcome.Benefit);
staticText = "goad each creature your opponents control that's enchanted by an Aura you control. " +
"Put a +1/+1 counter on {this} for each creature goaded this way";
}
private KaimaTheFracturedCalmEffect(final KaimaTheFracturedCalmEffect effect) {
super(effect);
}
@Override
public KaimaTheFracturedCalmEffect copy() {
return new KaimaTheFracturedCalmEffect(this);
}
@Override
public boolean apply(Game game, Ability source) {
int goaded = 0;
for (Permanent permanent : game.getBattlefield().getActivePermanents(
StaticFilters.FILTER_OPPONENTS_PERMANENT_CREATURE,
source.getControllerId(), source.getSourceId(), game
)) {
if (permanent
.getAttachments()
.stream()
.map(game::getPermanent)
.filter(Objects::nonNull)
.noneMatch(p -> p.isControlledBy(source.getControllerId())
&& p.hasSubtype(SubType.AURA, game))) {
continue;
}
game.addEffect(new GoadTargetEffect().setTargetPointer(new FixedTarget(permanent, game)), source);
goaded++;
}
if (goaded < 1) {
return false;
}
Permanent permanent = source.getSourcePermanentIfItStillExists(game);
if (permanent != null) {
permanent.addCounters(CounterType.P1P1.createInstance(goaded), source, game);
}
return true;
}
}

View file

@ -0,0 +1,137 @@
package mage.cards.k;
import mage.MageInt;
import mage.abilities.Ability;
import mage.abilities.TriggeredAbilityImpl;
import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.common.combat.GoadTargetEffect;
import mage.abilities.effects.common.continuous.BoostEquippedEffect;
import mage.abilities.effects.common.continuous.GainAbilityAttachedEffect;
import mage.abilities.keyword.MenaceAbility;
import mage.abilities.keyword.ReconfigureAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.*;
import mage.filter.StaticFilters;
import mage.game.Game;
import mage.game.events.DamagedEvent;
import mage.game.events.GameEvent;
import mage.game.permanent.Permanent;
import mage.target.targetpointer.FixedTarget;
import java.util.UUID;
/**
* @author TheElk801
*/
public final class KomainuBattleArmor extends CardImpl {
public KomainuBattleArmor(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT, CardType.CREATURE}, "{2}{R}");
this.subtype.add(SubType.EQUIPMENT);
this.subtype.add(SubType.DOG);
this.power = new MageInt(2);
this.toughness = new MageInt(2);
// Menace
this.addAbility(new MenaceAbility(false));
// Equipped creature gets +2/+2 and has menace.
Ability ability = new SimpleStaticAbility(new BoostEquippedEffect(2, 2));
ability.addEffect(new GainAbilityAttachedEffect(
new MenaceAbility(false), AttachmentType.EQUIPMENT
).setText("and has menace"));
this.addAbility(ability);
// Whenever Komainu Battle Armor or equipped creature deals combat damage to a player, goad each creature that player controls.
this.addAbility(new KomainuBattleArmorTriggeredAbility());
// Reconfigure {4}
this.addAbility(new ReconfigureAbility("{4}"));
}
private KomainuBattleArmor(final KomainuBattleArmor card) {
super(card);
}
@Override
public KomainuBattleArmor copy() {
return new KomainuBattleArmor(this);
}
}
class KomainuBattleArmorTriggeredAbility extends TriggeredAbilityImpl {
KomainuBattleArmorTriggeredAbility() {
super(Zone.BATTLEFIELD, new KomainuBattleArmorEffect());
}
private KomainuBattleArmorTriggeredAbility(final KomainuBattleArmorTriggeredAbility ability) {
super(ability);
}
@Override
public KomainuBattleArmorTriggeredAbility copy() {
return new KomainuBattleArmorTriggeredAbility(this);
}
@Override
public boolean checkEventType(GameEvent event, Game game) {
return false;
}
@Override
public boolean checkTrigger(GameEvent event, Game game) {
if (!((DamagedEvent) event).isCombatDamage()) {
return false;
}
if (getSourceId().equals(event.getSourceId())) {
getEffects().setTargetPointer(new FixedTarget(event.getTargetId()));
return true;
}
Permanent permanent = getSourcePermanentOrLKI(game);
if (permanent != null && event.getSourceId().equals(permanent.getAttachedTo())) {
getEffects().setTargetPointer(new FixedTarget(event.getTargetId()));
return true;
}
return false;
}
@Override
public String getRule() {
return "Whenever {this} or equipped creature deals combat damage to a player, goad each creature that player controls.";
}
}
class KomainuBattleArmorEffect extends OneShotEffect {
KomainuBattleArmorEffect() {
super(Outcome.Benefit);
}
private KomainuBattleArmorEffect(final KomainuBattleArmorEffect effect) {
super(effect);
}
@Override
public KomainuBattleArmorEffect copy() {
return new KomainuBattleArmorEffect(this);
}
@Override
public boolean apply(Game game, Ability source) {
UUID playerId = getTargetPointer().getFirst(game, source);
if (playerId == null) {
return false;
}
for (Permanent permanent : game.getBattlefield().getActivePermanents(
StaticFilters.FILTER_CONTROLLED_CREATURE,
playerId, source.getSourceId(), game
)) {
game.addEffect(new GoadTargetEffect().setTargetPointer(new FixedTarget(permanent, game)), source);
}
return true;
}
}

View file

@ -1,12 +1,10 @@
package mage.cards.m;
import java.util.UUID;
import mage.MageInt;
import mage.abilities.Ability;
import mage.abilities.common.DealsDamageToAPlayerAllTriggeredAbility;
import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.effects.ContinuousRuleModifyingEffectImpl;
import mage.abilities.effects.Effect;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.common.combat.GoadTargetEffect;
import mage.cards.CardImpl;
@ -15,8 +13,11 @@ import mage.constants.*;
import mage.filter.StaticFilters;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.permanent.Permanent;
import mage.target.targetpointer.FixedTarget;
import java.util.UUID;
/**
* @author TheElk801
*/
@ -86,8 +87,6 @@ class MarisiBreakerOfTheCoilSpellEffect extends ContinuousRuleModifyingEffectImp
class MarisiBreakerOfTheCoilEffect extends OneShotEffect {
private static final Effect effect = new GoadTargetEffect();
MarisiBreakerOfTheCoilEffect() {
super(Outcome.Benefit);
staticText = "goad each creature that player controls "
@ -105,13 +104,12 @@ class MarisiBreakerOfTheCoilEffect extends OneShotEffect {
@Override
public boolean apply(Game game, Ability source) {
game.getBattlefield().getAllActivePermanents(
for (Permanent permanent : game.getBattlefield().getAllActivePermanents(
StaticFilters.FILTER_PERMANENT_CREATURE,
targetPointer.getFirst(game, source), game
).stream().forEach(permanent -> {
effect.setTargetPointer(new FixedTarget(permanent, game));
effect.apply(game, source);
});
)) {
game.addEffect(new GoadTargetEffect().setTargetPointer(new FixedTarget(permanent, game)), source);
}
return true;
}
}

View file

@ -2,8 +2,9 @@ package mage.cards.m;
import mage.abilities.Ability;
import mage.abilities.common.AttacksAttachedTriggeredAbility;
import mage.abilities.common.GoadAttachedAbility;
import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.effects.common.AttachEffect;
import mage.abilities.effects.common.combat.GoadAttachedEffect;
import mage.abilities.effects.common.continuous.BoostAllEffect;
import mage.abilities.effects.common.continuous.BoostEnchantedEffect;
import mage.abilities.keyword.EnchantAbility;
@ -44,7 +45,9 @@ public final class MartialImpetus extends CardImpl {
this.addAbility(ability);
// Enchanted creature gets +1/+1 and is goaded.
this.addAbility(new GoadAttachedAbility(new BoostEnchantedEffect(1, 1)));
ability = new SimpleStaticAbility(new BoostEnchantedEffect(1, 1));
ability.addEffect(new GoadAttachedEffect());
this.addAbility(ability);
// Whenever enchanted creature attacks, each other creature that's attacking one of your opponents gets +1/+1 until end of turn.
this.addAbility(new AttacksAttachedTriggeredAbility(

View file

@ -2,10 +2,11 @@ package mage.cards.p;
import mage.abilities.Ability;
import mage.abilities.common.AttacksAttachedTriggeredAbility;
import mage.abilities.common.GoadAttachedAbility;
import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.effects.common.AttachEffect;
import mage.abilities.effects.common.GainLifeEffect;
import mage.abilities.effects.common.LoseLifeControllerAttachedEffect;
import mage.abilities.effects.common.combat.GoadAttachedEffect;
import mage.abilities.effects.common.continuous.BoostEnchantedEffect;
import mage.abilities.keyword.EnchantAbility;
import mage.cards.CardImpl;
@ -37,7 +38,9 @@ public final class ParasiticImpetus extends CardImpl {
this.addAbility(ability);
// Enchanted creature gets +2/+2 and is goaded.
this.addAbility(new GoadAttachedAbility(new BoostEnchantedEffect(2, 2)));
ability = new SimpleStaticAbility(new BoostEnchantedEffect(2, 2));
ability.addEffect(new GoadAttachedEffect());
this.addAbility(ability);
// Whenever enchanted creature attacks, its controller loses 2 life and you gain 2 life.
ability = new AttacksAttachedTriggeredAbility(

View file

@ -109,7 +109,6 @@ class PortalMageEffect extends OneShotEffect {
}
// Select the new defender
TargetDefender target = new TargetDefender(defenders, null);
target.setNotTarget(true); // player or planswalker hexproof does not prevent attacking a player
if (controller.chooseTarget(Outcome.Damage, target, source, game)) {
if (!combatGroupTarget.getDefenderId().equals(target.getFirstTarget())) {
if (combatGroupTarget.changeDefenderPostDeclaration(target.getFirstTarget(), game)) {

View file

@ -1,9 +1,10 @@
package mage.cards.p;
import mage.abilities.Ability;
import mage.abilities.common.GoadAttachedAbility;
import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.effects.RequirementEffect;
import mage.abilities.effects.common.AttachEffect;
import mage.abilities.effects.common.combat.GoadAttachedEffect;
import mage.abilities.effects.common.continuous.BoostEnchantedEffect;
import mage.abilities.keyword.EnchantAbility;
import mage.cards.CardImpl;
@ -37,11 +38,10 @@ public final class PredatoryImpetus extends CardImpl {
this.addAbility(ability);
// Enchanted creature gets +3/+3, must be blocked if able, and is goaded.
this.addAbility(new GoadAttachedAbility(
new BoostEnchantedEffect(3, 3)
.setText("Enchanted creature gets +3/+3"),
new PredatoryImpetusEffect()
));
ability = new SimpleStaticAbility(new BoostEnchantedEffect(3, 3));
ability.addEffect(new PredatoryImpetusEffect());
ability.addEffect(new GoadAttachedEffect().concatBy(","));
this.addAbility(ability);
}
private PredatoryImpetus(final PredatoryImpetus card) {

View file

@ -2,8 +2,9 @@ package mage.cards.p;
import mage.abilities.Ability;
import mage.abilities.common.AttacksAttachedTriggeredAbility;
import mage.abilities.common.GoadAttachedAbility;
import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.effects.common.AttachEffect;
import mage.abilities.effects.common.combat.GoadAttachedEffect;
import mage.abilities.effects.common.continuous.BoostEnchantedEffect;
import mage.abilities.effects.keyword.ScryEffect;
import mage.abilities.keyword.EnchantAbility;
@ -36,7 +37,9 @@ public final class PsychicImpetus extends CardImpl {
this.addAbility(ability);
// Enchanted creature gets +2/+2 and is goaded.
this.addAbility(new GoadAttachedAbility(new BoostEnchantedEffect(2, 2)));
ability = new SimpleStaticAbility(new BoostEnchantedEffect(2, 2));
ability.addEffect(new GoadAttachedEffect());
this.addAbility(ability);
// Whenever enchanted creature attacks, you scry 2.
this.addAbility(new AttacksAttachedTriggeredAbility(

View file

@ -2,9 +2,10 @@ package mage.cards.s;
import mage.abilities.Ability;
import mage.abilities.common.AttacksAttachedTriggeredAbility;
import mage.abilities.common.GoadAttachedAbility;
import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.effects.common.AttachEffect;
import mage.abilities.effects.common.CreateTokenEffect;
import mage.abilities.effects.common.combat.GoadAttachedEffect;
import mage.abilities.effects.common.continuous.BoostEnchantedEffect;
import mage.abilities.keyword.EnchantAbility;
import mage.cards.CardImpl;
@ -37,7 +38,9 @@ public final class ShinyImpetus extends CardImpl {
this.addAbility(ability);
// Enchanted creature gets +2/+2 and is goaded.
this.addAbility(new GoadAttachedAbility(new BoostEnchantedEffect(2, 2)));
ability = new SimpleStaticAbility(new BoostEnchantedEffect(2, 2));
ability.addEffect(new GoadAttachedEffect());
this.addAbility(ability);
// Whenever enchanted creature attacks, you create a Treasure token.
this.addAbility(new AttacksAttachedTriggeredAbility(

View file

@ -0,0 +1,106 @@
package mage.cards.v;
import mage.MageInt;
import mage.abilities.Ability;
import mage.abilities.common.AttacksAllTriggeredAbility;
import mage.abilities.common.EntersBattlefieldOrAttacksSourceTriggeredAbility;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.common.combat.GoadTargetEffect;
import mage.abilities.keyword.FlyingAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.Outcome;
import mage.constants.SetTargetPointer;
import mage.constants.SubType;
import mage.filter.common.FilterCreaturePermanent;
import mage.filter.predicate.Predicate;
import mage.game.Game;
import mage.game.permanent.Permanent;
import mage.players.Player;
import mage.target.common.TargetCreaturePermanent;
import java.util.UUID;
/**
* @author TheElk801
*/
public final class VengefulAncestor extends CardImpl {
private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("a goaded creature");
static {
filter.add(VengefulAncestorPredicate.instance);
}
public VengefulAncestor(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{R}{R}");
this.subtype.add(SubType.SPIRIT);
this.subtype.add(SubType.DRAGON);
this.power = new MageInt(3);
this.toughness = new MageInt(4);
// Flying
this.addAbility(FlyingAbility.getInstance());
// Whenever Vengeful Ancestor enters the battlefield or attacks, goad target creature.
Ability ability = new EntersBattlefieldOrAttacksSourceTriggeredAbility(new GoadTargetEffect());
ability.addTarget(new TargetCreaturePermanent());
this.addAbility(ability);
// Whenever a goaded creature attacks, it deals 1 damage to its controller.
this.addAbility(new AttacksAllTriggeredAbility(
new VengefulAncestorEffect(), false, filter,
SetTargetPointer.NONE, false
));
}
private VengefulAncestor(final VengefulAncestor card) {
super(card);
}
@Override
public VengefulAncestor copy() {
return new VengefulAncestor(this);
}
}
enum VengefulAncestorPredicate implements Predicate<Permanent> {
instance;
@Override
public boolean apply(Permanent input, Game game) {
return !input.getGoadingPlayers().isEmpty();
}
}
class VengefulAncestorEffect extends OneShotEffect {
VengefulAncestorEffect() {
super(Outcome.Benefit);
staticText = "it deals 1 damage to its controller";
}
private VengefulAncestorEffect(final VengefulAncestorEffect effect) {
super(effect);
}
@Override
public VengefulAncestorEffect copy() {
return new VengefulAncestorEffect(this);
}
@Override
public boolean apply(Game game, Ability source) {
Permanent permanent = (Permanent) getValue("attacker");
if (permanent == null) {
return false;
}
Player player = game.getPlayer(permanent.getControllerId());
if (player == null) {
return false;
}
return player.damage(1, permanent.getId(), source, game) > 0;
}
}

View file

@ -273,6 +273,7 @@ public final class ForgottenRealmsCommander extends ExpansionSet {
cards.add(new SetCardInfo("Valorous Stance", 76, Rarity.UNCOMMON, mage.cards.v.ValorousStance.class));
cards.add(new SetCardInfo("Vandalblast", 148, Rarity.UNCOMMON, mage.cards.v.Vandalblast.class));
cards.add(new SetCardInfo("Vanish into Memory", 196, Rarity.UNCOMMON, mage.cards.v.VanishIntoMemory.class));
cards.add(new SetCardInfo("Vengeful Ancestor", 35, Rarity.RARE, mage.cards.v.VengefulAncestor.class));
cards.add(new SetCardInfo("Verdant Embrace", 173, Rarity.RARE, mage.cards.v.VerdantEmbrace.class));
cards.add(new SetCardInfo("Victimize", 112, Rarity.UNCOMMON, mage.cards.v.Victimize.class));
cards.add(new SetCardInfo("Viridian Longbow", 221, Rarity.COMMON, mage.cards.v.ViridianLongbow.class));

View file

@ -19,16 +19,20 @@ public final class NeonDynastyCommander extends ExpansionSet {
super("Neon Dynasty Commander", "NEC", ExpansionSet.buildDate(2022, 2, 18), SetType.SUPPLEMENTAL);
this.hasBasicLands = false;
cards.add(new SetCardInfo("Access Denied", 11, Rarity.RARE, mage.cards.a.AccessDenied.class));
cards.add(new SetCardInfo("Access Denied", 11, Rarity.RARE, mage.cards.a.AccessDenied.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("Access Denied", 47, Rarity.RARE, mage.cards.a.AccessDenied.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("Acidic Slime", 112, Rarity.UNCOMMON, mage.cards.a.AcidicSlime.class));
cards.add(new SetCardInfo("Aerial Surveyor", 5, Rarity.RARE, mage.cards.a.AerialSurveyor.class));
cards.add(new SetCardInfo("Aerial Surveyor", 39, Rarity.RARE, mage.cards.a.AerialSurveyor.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("Aerial Surveyor", 5, Rarity.RARE, mage.cards.a.AerialSurveyor.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("Aeronaut Admiral", 79, Rarity.UNCOMMON, mage.cards.a.AeronautAdmiral.class));
cards.add(new SetCardInfo("Agitator Ant", 102, Rarity.RARE, mage.cards.a.AgitatorAnt.class));
cards.add(new SetCardInfo("Akki Battle Squad", 18, Rarity.RARE, mage.cards.a.AkkiBattleSquad.class));
cards.add(new SetCardInfo("Akki Battle Squad", 18, Rarity.RARE, mage.cards.a.AkkiBattleSquad.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("Akki Battle Squad", 57, Rarity.RARE, mage.cards.a.AkkiBattleSquad.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("Arcane Signet", 144, Rarity.COMMON, mage.cards.a.ArcaneSignet.class));
cards.add(new SetCardInfo("Arcanist's Owl", 135, Rarity.UNCOMMON, mage.cards.a.ArcanistsOwl.class));
cards.add(new SetCardInfo("Armed and Armored", 80, Rarity.UNCOMMON, mage.cards.a.ArmedAndArmored.class));
cards.add(new SetCardInfo("Ascendant Acolyte", 24, Rarity.RARE, mage.cards.a.AscendantAcolyte.class));
cards.add(new SetCardInfo("Ascendant Acolyte", 24, Rarity.RARE, mage.cards.a.AscendantAcolyte.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("Ascendant Acolyte", 64, Rarity.RARE, mage.cards.a.AscendantAcolyte.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("Azorius Signet", 145, Rarity.UNCOMMON, mage.cards.a.AzoriusSignet.class));
cards.add(new SetCardInfo("Bear Umbra", 113, Rarity.RARE, mage.cards.b.BearUmbra.class));
cards.add(new SetCardInfo("Beast Within", 114, Rarity.UNCOMMON, mage.cards.b.BeastWithin.class));
@ -38,19 +42,25 @@ public final class NeonDynastyCommander extends ExpansionSet {
cards.add(new SetCardInfo("Chain Reaction", 103, Rarity.RARE, mage.cards.c.ChainReaction.class));
cards.add(new SetCardInfo("Champion of Lambholt", 115, Rarity.RARE, mage.cards.c.ChampionOfLambholt.class));
cards.add(new SetCardInfo("Chaos Warp", 104, Rarity.RARE, mage.cards.c.ChaosWarp.class));
cards.add(new SetCardInfo("Chishiro, the Shattered Blade", 1, Rarity.MYTHIC, mage.cards.c.ChishiroTheShatteredBlade.class));
cards.add(new SetCardInfo("Chishiro, the Shattered Blade", 1, Rarity.MYTHIC, mage.cards.c.ChishiroTheShatteredBlade.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("Chishiro, the Shattered Blade", 73, Rarity.MYTHIC, mage.cards.c.ChishiroTheShatteredBlade.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("Chishiro, the Shattered Blade", 77, Rarity.MYTHIC, mage.cards.c.ChishiroTheShatteredBlade.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("Cinder Glade", 166, Rarity.RARE, mage.cards.c.CinderGlade.class));
cards.add(new SetCardInfo("Collision of Realms", 19, Rarity.RARE, mage.cards.c.CollisionOfRealms.class));
cards.add(new SetCardInfo("Collision of Realms", 19, Rarity.RARE, mage.cards.c.CollisionOfRealms.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("Collision of Realms", 58, Rarity.RARE, mage.cards.c.CollisionOfRealms.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("Colossal Plow", 148, Rarity.UNCOMMON, mage.cards.c.ColossalPlow.class));
cards.add(new SetCardInfo("Command Tower", 167, Rarity.COMMON, mage.cards.c.CommandTower.class));
cards.add(new SetCardInfo("Concord with the Kami", 25, Rarity.RARE, mage.cards.c.ConcordWithTheKami.class));
cards.add(new SetCardInfo("Concord with the Kami", 25, Rarity.RARE, mage.cards.c.ConcordWithTheKami.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("Concord with the Kami", 65, Rarity.RARE, mage.cards.c.ConcordWithTheKami.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("Crush Contraband", 82, Rarity.UNCOMMON, mage.cards.c.CrushContraband.class));
cards.add(new SetCardInfo("Cultivator's Caravan", 149, Rarity.RARE, mage.cards.c.CultivatorsCaravan.class));
cards.add(new SetCardInfo("Cyberdrive Awakener", 12, Rarity.RARE, mage.cards.c.CyberdriveAwakener.class));
cards.add(new SetCardInfo("Cyberdrive Awakener", 12, Rarity.RARE, mage.cards.c.CyberdriveAwakener.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("Cyberdrive Awakener", 48, Rarity.RARE, mage.cards.c.CyberdriveAwakener.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("Dance of the Manse", 136, Rarity.RARE, mage.cards.d.DanceOfTheManse.class));
cards.add(new SetCardInfo("Decimate", 137, Rarity.RARE, mage.cards.d.Decimate.class));
cards.add(new SetCardInfo("Dispatch", 83, Rarity.UNCOMMON, mage.cards.d.Dispatch.class));
cards.add(new SetCardInfo("Drumbellower", 6, Rarity.RARE, mage.cards.d.Drumbellower.class));
cards.add(new SetCardInfo("Drumbellower", 40, Rarity.RARE, mage.cards.d.Drumbellower.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("Drumbellower", 6, Rarity.RARE, mage.cards.d.Drumbellower.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("Elemental Mastery", 105, Rarity.RARE, mage.cards.e.ElementalMastery.class));
cards.add(new SetCardInfo("Emry, Lurker of the Loch", 91, Rarity.RARE, mage.cards.e.EmryLurkerOfTheLoch.class));
cards.add(new SetCardInfo("Etherium Sculptor", 92, Rarity.COMMON, mage.cards.e.EtheriumSculptor.class));
@ -63,41 +73,61 @@ public final class NeonDynastyCommander extends ExpansionSet {
cards.add(new SetCardInfo("Game Trail", 169, Rarity.RARE, mage.cards.g.GameTrail.class));
cards.add(new SetCardInfo("Generous Gift", 84, Rarity.UNCOMMON, mage.cards.g.GenerousGift.class));
cards.add(new SetCardInfo("Genesis Hydra", 118, Rarity.RARE, mage.cards.g.GenesisHydra.class));
cards.add(new SetCardInfo("Go-Shintai of Life's Origin", 37, Rarity.MYTHIC, mage.cards.g.GoShintaiOfLifesOrigin.class));
cards.add(new SetCardInfo("Go-Shintai of Life's Origin", 37, Rarity.MYTHIC, mage.cards.g.GoShintaiOfLifesOrigin.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("Go-Shintai of Life's Origin", 66, Rarity.MYTHIC, mage.cards.g.GoShintaiOfLifesOrigin.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("Goblin Razerunners", 106, Rarity.RARE, mage.cards.g.GoblinRazerunners.class));
cards.add(new SetCardInfo("Gold Myr", 153, Rarity.COMMON, mage.cards.g.GoldMyr.class));
cards.add(new SetCardInfo("Grumgully, the Generous", 138, Rarity.UNCOMMON, mage.cards.g.GrumgullyTheGenerous.class));
cards.add(new SetCardInfo("Gruul Turf", 170, Rarity.UNCOMMON, mage.cards.g.GruulTurf.class));
cards.add(new SetCardInfo("Hanna, Ship's Navigator", 139, Rarity.RARE, mage.cards.h.HannaShipsNavigator.class));
cards.add(new SetCardInfo("Hunter's Insight", 119, Rarity.UNCOMMON, mage.cards.h.HuntersInsight.class));
cards.add(new SetCardInfo("Imposter Mech", 13, Rarity.RARE, mage.cards.i.ImposterMech.class));
cards.add(new SetCardInfo("Imposter Mech", 13, Rarity.RARE, mage.cards.i.ImposterMech.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("Imposter Mech", 49, Rarity.RARE, mage.cards.i.ImposterMech.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("Indomitable Archangel", 85, Rarity.MYTHIC, mage.cards.i.IndomitableArchangel.class));
cards.add(new SetCardInfo("Ironsoul Enforcer", 7, Rarity.RARE, mage.cards.i.IronsoulEnforcer.class));
cards.add(new SetCardInfo("Ironsoul Enforcer", 41, Rarity.RARE, mage.cards.i.IronsoulEnforcer.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("Ironsoul Enforcer", 7, Rarity.RARE, mage.cards.i.IronsoulEnforcer.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("Jace, Architect of Thought", 93, Rarity.MYTHIC, mage.cards.j.JaceArchitectOfThought.class));
cards.add(new SetCardInfo("Kami of Celebration", 20, Rarity.RARE, mage.cards.k.KamiOfCelebration.class));
cards.add(new SetCardInfo("Kappa Cannoneer", 14, Rarity.RARE, mage.cards.k.KappaCannoneer.class));
cards.add(new SetCardInfo("Katsumasa, the Animator", 15, Rarity.RARE, mage.cards.k.KatsumasaTheAnimator.class));
cards.add(new SetCardInfo("Kaima, the Fractured Calm", 3, Rarity.MYTHIC, mage.cards.k.KaimaTheFracturedCalm.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("Kaima, the Fractured Calm", 74, Rarity.MYTHIC, mage.cards.k.KaimaTheFracturedCalm.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("Kami of Celebration", 20, Rarity.RARE, mage.cards.k.KamiOfCelebration.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("Kami of Celebration", 59, Rarity.RARE, mage.cards.k.KamiOfCelebration.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("Kappa Cannoneer", 14, Rarity.RARE, mage.cards.k.KappaCannoneer.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("Kappa Cannoneer", 50, Rarity.RARE, mage.cards.k.KappaCannoneer.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("Katsumasa, the Animator", 15, Rarity.RARE, mage.cards.k.KatsumasaTheAnimator.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("Katsumasa, the Animator", 51, Rarity.RARE, mage.cards.k.KatsumasaTheAnimator.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("Kodama's Reach", 120, Rarity.COMMON, mage.cards.k.KodamasReach.class));
cards.add(new SetCardInfo("Kosei, Penitent Warlord", 26, Rarity.RARE, mage.cards.k.KoseiPenitentWarlord.class));
cards.add(new SetCardInfo("Kotori, Pilot Prodigy", 2, Rarity.MYTHIC, mage.cards.k.KotoriPilotProdigy.class));
cards.add(new SetCardInfo("Komainu Battle Armor", 21, Rarity.RARE, mage.cards.k.KomainuBattleArmor.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("Komainu Battle Armor", 60, Rarity.RARE, mage.cards.k.KomainuBattleArmor.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("Kosei, Penitent Warlord", 26, Rarity.RARE, mage.cards.k.KoseiPenitentWarlord.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("Kosei, Penitent Warlord", 67, Rarity.RARE, mage.cards.k.KoseiPenitentWarlord.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("Kotori, Pilot Prodigy", 2, Rarity.MYTHIC, mage.cards.k.KotoriPilotProdigy.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("Kotori, Pilot Prodigy", 75, Rarity.MYTHIC, mage.cards.k.KotoriPilotProdigy.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("Kotori, Pilot Prodigy", 78, Rarity.MYTHIC, mage.cards.k.KotoriPilotProdigy.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("Krenko, Tin Street Kingpin", 107, Rarity.RARE, mage.cards.k.KrenkoTinStreetKingpin.class));
cards.add(new SetCardInfo("Loyal Guardian", 121, Rarity.UNCOMMON, mage.cards.l.LoyalGuardian.class));
cards.add(new SetCardInfo("Mage Slayer", 140, Rarity.UNCOMMON, mage.cards.m.MageSlayer.class));
cards.add(new SetCardInfo("Master of Etherium", 94, Rarity.RARE, mage.cards.m.MasterOfEtherium.class));
cards.add(new SetCardInfo("Mirage Mirror", 154, Rarity.RARE, mage.cards.m.MirageMirror.class));
cards.add(new SetCardInfo("Mossfire Valley", 171, Rarity.RARE, mage.cards.m.MossfireValley.class));
cards.add(new SetCardInfo("Myojin of Blooming Dawn", 31, Rarity.RARE, mage.cards.m.MyojinOfBloomingDawn.class));
cards.add(new SetCardInfo("Myojin of Cryptic Dreams", 33, Rarity.RARE, mage.cards.m.MyojinOfCrypticDreams.class));
cards.add(new SetCardInfo("Myojin of Grim Betrayal", 34, Rarity.RARE, mage.cards.m.MyojinOfGrimBetrayal.class));
cards.add(new SetCardInfo("Myojin of Roaring Blades", 36, Rarity.RARE, mage.cards.m.MyojinOfRoaringBlades.class));
cards.add(new SetCardInfo("Myojin of Towering Might", 38, Rarity.RARE, mage.cards.m.MyojinOfToweringMight.class));
cards.add(new SetCardInfo("Myojin of Blooming Dawn", 31, Rarity.RARE, mage.cards.m.MyojinOfBloomingDawn.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("Myojin of Blooming Dawn", 42, Rarity.RARE, mage.cards.m.MyojinOfBloomingDawn.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("Myojin of Cryptic Dreams", 33, Rarity.RARE, mage.cards.m.MyojinOfCrypticDreams.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("Myojin of Cryptic Dreams", 52, Rarity.RARE, mage.cards.m.MyojinOfCrypticDreams.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("Myojin of Grim Betrayal", 34, Rarity.RARE, mage.cards.m.MyojinOfGrimBetrayal.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("Myojin of Grim Betrayal", 55, Rarity.RARE, mage.cards.m.MyojinOfGrimBetrayal.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("Myojin of Roaring Blades", 36, Rarity.RARE, mage.cards.m.MyojinOfRoaringBlades.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("Myojin of Roaring Blades", 61, Rarity.RARE, mage.cards.m.MyojinOfRoaringBlades.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("Myojin of Towering Might", 38, Rarity.RARE, mage.cards.m.MyojinOfToweringMight.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("Myojin of Towering Might", 68, Rarity.RARE, mage.cards.m.MyojinOfToweringMight.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("Myrsmith", 86, Rarity.UNCOMMON, mage.cards.m.Myrsmith.class));
cards.add(new SetCardInfo("Nissa, Voice of Zendikar", 122, Rarity.MYTHIC, mage.cards.n.NissaVoiceOfZendikar.class));
cards.add(new SetCardInfo("One with the Kami", 27, Rarity.RARE, mage.cards.o.OneWithTheKami.class));
cards.add(new SetCardInfo("One with the Kami", 27, Rarity.RARE, mage.cards.o.OneWithTheKami.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("One with the Kami", 69, Rarity.RARE, mage.cards.o.OneWithTheKami.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("Opal Palace", 172, Rarity.COMMON, mage.cards.o.OpalPalace.class));
cards.add(new SetCardInfo("Oran-Rief, the Vastwood", 173, Rarity.RARE, mage.cards.o.OranRiefTheVastwood.class));
cards.add(new SetCardInfo("Ordeal of Nylea", 123, Rarity.UNCOMMON, mage.cards.o.OrdealOfNylea.class));
cards.add(new SetCardInfo("Organic Extinction", 8, Rarity.RARE, mage.cards.o.OrganicExtinction.class));
cards.add(new SetCardInfo("Organic Extinction", 43, Rarity.RARE, mage.cards.o.OrganicExtinction.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("Organic Extinction", 8, Rarity.RARE, mage.cards.o.OrganicExtinction.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("Ox of Agonas", 108, Rarity.MYTHIC, mage.cards.o.OxOfAgonas.class));
cards.add(new SetCardInfo("Parhelion II", 87, Rarity.RARE, mage.cards.p.ParhelionII.class));
cards.add(new SetCardInfo("Peacewalker Colossus", 155, Rarity.RARE, mage.cards.p.PeacewalkerColossus.class));
@ -108,26 +138,33 @@ public final class NeonDynastyCommander extends ExpansionSet {
cards.add(new SetCardInfo("Raging Ravine", 176, Rarity.RARE, mage.cards.r.RagingRavine.class));
cards.add(new SetCardInfo("Raiders' Karve", 156, Rarity.COMMON, mage.cards.r.RaidersKarve.class));
cards.add(new SetCardInfo("Rampant Growth", 125, Rarity.COMMON, mage.cards.r.RampantGrowth.class));
cards.add(new SetCardInfo("Rampant Rejuvenator", 28, Rarity.RARE, mage.cards.r.RampantRejuvenator.class));
cards.add(new SetCardInfo("Rampant Rejuvenator", 28, Rarity.RARE, mage.cards.r.RampantRejuvenator.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("Rampant Rejuvenator", 70, Rarity.RARE, mage.cards.r.RampantRejuvenator.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("Reality Shift", 95, Rarity.UNCOMMON, mage.cards.r.RealityShift.class));
cards.add(new SetCardInfo("Release to Memory", 9, Rarity.RARE, mage.cards.r.ReleaseToMemory.class));
cards.add(new SetCardInfo("Research Thief", 16, Rarity.RARE, mage.cards.r.ResearchThief.class));
cards.add(new SetCardInfo("Release to Memory", 44, Rarity.RARE, mage.cards.r.ReleaseToMemory.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("Release to Memory", 9, Rarity.RARE, mage.cards.r.ReleaseToMemory.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("Research Thief", 16, Rarity.RARE, mage.cards.r.ResearchThief.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("Research Thief", 53, Rarity.RARE, mage.cards.r.ResearchThief.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("Rhythm of the Wild", 142, Rarity.UNCOMMON, mage.cards.r.RhythmOfTheWild.class));
cards.add(new SetCardInfo("Riddlesmith", 96, Rarity.UNCOMMON, mage.cards.r.Riddlesmith.class));
cards.add(new SetCardInfo("Rishkar's Expertise", 127, Rarity.RARE, mage.cards.r.RishkarsExpertise.class));
cards.add(new SetCardInfo("Rishkar, Peema Renegade", 126, Rarity.RARE, mage.cards.r.RishkarPeemaRenegade.class));
cards.add(new SetCardInfo("Ruthless Technomancer", 35, Rarity.RARE, mage.cards.r.RuthlessTechnomancer.class));
cards.add(new SetCardInfo("Ruthless Technomancer", 35, Rarity.RARE, mage.cards.r.RuthlessTechnomancer.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("Ruthless Technomancer", 56, Rarity.RARE, mage.cards.r.RuthlessTechnomancer.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("Sai, Master Thopterist", 97, Rarity.RARE, mage.cards.s.SaiMasterThopterist.class));
cards.add(new SetCardInfo("Sakura-Tribe Elder", 128, Rarity.COMMON, mage.cards.s.SakuraTribeElder.class));
cards.add(new SetCardInfo("Shamanic Revelation", 129, Rarity.RARE, mage.cards.s.ShamanicRevelation.class));
cards.add(new SetCardInfo("Shifting Shadow", 109, Rarity.RARE, mage.cards.s.ShiftingShadow.class));
cards.add(new SetCardInfo("Shimmer Myr", 157, Rarity.UNCOMMON, mage.cards.s.ShimmerMyr.class));
cards.add(new SetCardInfo("Shorikai, Genesis Engine", 4, Rarity.MYTHIC, mage.cards.s.ShorikaiGenesisEngine.class));
cards.add(new SetCardInfo("Silkguard", 29, Rarity.RARE, mage.cards.s.Silkguard.class));
cards.add(new SetCardInfo("Shorikai, Genesis Engine", 4, Rarity.MYTHIC, mage.cards.s.ShorikaiGenesisEngine.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("Shorikai, Genesis Engine", 76, Rarity.MYTHIC, mage.cards.s.ShorikaiGenesisEngine.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("Silkguard", 29, Rarity.RARE, mage.cards.s.Silkguard.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("Silkguard", 71, Rarity.RARE, mage.cards.s.Silkguard.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("Silver Myr", 158, Rarity.COMMON, mage.cards.s.SilverMyr.class));
cards.add(new SetCardInfo("Skycloud Expanse", 177, Rarity.RARE, mage.cards.s.SkycloudExpanse.class));
cards.add(new SetCardInfo("Skysovereign, Consul Flagship", 159, Rarity.MYTHIC, mage.cards.s.SkysovereignConsulFlagship.class));
cards.add(new SetCardInfo("Smoke Spirits' Aid", 22, Rarity.RARE, mage.cards.s.SmokeSpiritsAid.class));
cards.add(new SetCardInfo("Smoke Spirits' Aid", 22, Rarity.RARE, mage.cards.s.SmokeSpiritsAid.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("Smoke Spirits' Aid", 62, Rarity.RARE, mage.cards.s.SmokeSpiritsAid.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("Smuggler's Copter", 160, Rarity.RARE, mage.cards.s.SmugglersCopter.class));
cards.add(new SetCardInfo("Snake Umbra", 130, Rarity.UNCOMMON, mage.cards.s.SnakeUmbra.class));
cards.add(new SetCardInfo("Sol Ring", 161, Rarity.UNCOMMON, mage.cards.s.SolRing.class));
@ -137,11 +174,13 @@ public final class NeonDynastyCommander extends ExpansionSet {
cards.add(new SetCardInfo("Spire of Industry", 178, Rarity.RARE, mage.cards.s.SpireOfIndustry.class));
cards.add(new SetCardInfo("Sram, Senior Edificer", 88, Rarity.RARE, mage.cards.s.SramSeniorEdificer.class));
cards.add(new SetCardInfo("Starstorm", 110, Rarity.RARE, mage.cards.s.Starstorm.class));
cards.add(new SetCardInfo("Swift Reconfiguration", 10, Rarity.RARE, mage.cards.s.SwiftReconfiguration.class));
cards.add(new SetCardInfo("Swift Reconfiguration", 10, Rarity.RARE, mage.cards.s.SwiftReconfiguration.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("Swift Reconfiguration", 45, Rarity.RARE, mage.cards.s.SwiftReconfiguration.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("Swiftfoot Boots", 163, Rarity.UNCOMMON, mage.cards.s.SwiftfootBoots.class));
cards.add(new SetCardInfo("Sword of Vengeance", 164, Rarity.RARE, mage.cards.s.SwordOfVengeance.class));
cards.add(new SetCardInfo("Swords to Plowshares", 89, Rarity.UNCOMMON, mage.cards.s.SwordsToPlowshares.class));
cards.add(new SetCardInfo("Tanuki Transplanter", 30, Rarity.RARE, mage.cards.t.TanukiTransplanter.class));
cards.add(new SetCardInfo("Tanuki Transplanter", 30, Rarity.RARE, mage.cards.t.TanukiTransplanter.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("Tanuki Transplanter", 72, Rarity.RARE, mage.cards.t.TanukiTransplanter.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("Taurean Mauler", 111, Rarity.RARE, mage.cards.t.TaureanMauler.class));
cards.add(new SetCardInfo("Temple of Abandon", 179, Rarity.RARE, mage.cards.t.TempleOfAbandon.class));
cards.add(new SetCardInfo("Temple of Enlightenment", 180, Rarity.RARE, mage.cards.t.TempleOfEnlightenment.class));
@ -149,13 +188,16 @@ public final class NeonDynastyCommander extends ExpansionSet {
cards.add(new SetCardInfo("Thopter Spy Network", 98, Rarity.RARE, mage.cards.t.ThopterSpyNetwork.class));
cards.add(new SetCardInfo("Thoughtcast", 99, Rarity.COMMON, mage.cards.t.Thoughtcast.class));
cards.add(new SetCardInfo("Ulasht, the Hate Seed", 143, Rarity.RARE, mage.cards.u.UlashtTheHateSeed.class));
cards.add(new SetCardInfo("Universal Surveillance", 17, Rarity.RARE, mage.cards.u.UniversalSurveillance.class));
cards.add(new SetCardInfo("Unquenchable Fury", 23, Rarity.RARE, mage.cards.u.UnquenchableFury.class));
cards.add(new SetCardInfo("Universal Surveillance", 17, Rarity.RARE, mage.cards.u.UniversalSurveillance.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("Universal Surveillance", 54, Rarity.RARE, mage.cards.u.UniversalSurveillance.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("Unquenchable Fury", 23, Rarity.RARE, mage.cards.u.UnquenchableFury.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("Unquenchable Fury", 63, Rarity.RARE, mage.cards.u.UnquenchableFury.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("Vastwood Surge", 133, Rarity.UNCOMMON, mage.cards.v.VastwoodSurge.class));
cards.add(new SetCardInfo("Vedalken Engineer", 100, Rarity.COMMON, mage.cards.v.VedalkenEngineer.class));
cards.add(new SetCardInfo("Weatherlight", 165, Rarity.MYTHIC, mage.cards.w.Weatherlight.class));
cards.add(new SetCardInfo("Whiptongue Hydra", 134, Rarity.RARE, mage.cards.w.WhiptongueHydra.class));
cards.add(new SetCardInfo("Whirler Rogue", 101, Rarity.UNCOMMON, mage.cards.w.WhirlerRogue.class));
cards.add(new SetCardInfo("Yoshimaru, Ever Faithful", 32, Rarity.MYTHIC, mage.cards.y.YoshimaruEverFaithful.class));
cards.add(new SetCardInfo("Yoshimaru, Ever Faithful", 32, Rarity.MYTHIC, mage.cards.y.YoshimaruEverFaithful.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("Yoshimaru, Ever Faithful", 46, Rarity.MYTHIC, mage.cards.y.YoshimaruEverFaithful.class, NON_FULL_USE_VARIOUS));
}
}

View file

@ -1,6 +1,5 @@
package org.mage.test.cards.abilities.keywords;
import java.io.FileNotFoundException;
import mage.constants.MultiplayerAttackOption;
import mage.constants.PhaseStep;
import mage.constants.RangeOfInfluence;
@ -10,16 +9,29 @@ import mage.game.Game;
import mage.game.GameException;
import mage.game.mulligan.MulliganType;
import mage.game.permanent.Permanent;
import mage.players.Player;
import org.junit.Assert;
import org.junit.Test;
import org.mage.test.player.TestPlayer;
import org.mage.test.serverside.base.CardTestMultiPlayerBase;
import java.io.FileNotFoundException;
import java.util.Arrays;
import java.util.UUID;
import java.util.stream.Collectors;
/**
*
* @author LevelX2
*/
public class GoadTest extends CardTestMultiPlayerBase {
private static final String marisi = "Marisi, Breaker of the Coil";
private static final String griffin = "Abbey Griffin";
private static final String ray = "Ray of Command";
private static final String homunculus = "Jeering Homunculus";
private static final String lion = "Silvercoat Lion";
private static final String archon = "Blazing Archon";
@Override
protected Game createNewGameAndPlayers() throws GameException, FileNotFoundException {
Game game = new FreeForAll(MultiplayerAttackOption.MULTIPLE, RangeOfInfluence.ALL, MulliganType.GAME_DEFAULT.getMulligan(0), 40);
@ -31,85 +43,155 @@ public class GoadTest extends CardTestMultiPlayerBase {
return game;
}
private void assertAttacking(String attacker, TestPlayer... players) {
Assert.assertTrue("At least one player should be provided", players.length > 0);
Permanent permanent = getPermanent(attacker);
Assert.assertTrue("Creature should be tapped", permanent.isTapped());
Assert.assertTrue("Creature should be attacking", permanent.isAttacking());
UUID defenderId = currentGame.getCombat().getDefenderId(permanent.getId());
Assert.assertTrue(
"Creature should be attacking one the following players: "
+ Arrays
.stream(players)
.map(Player::getName)
.reduce((a, b) -> a + ", " + b)
.orElse(""),
Arrays.stream(players)
.map(TestPlayer::getId)
.anyMatch(defenderId::equals)
);
}
private void assertGoaded(String attacker, TestPlayer... players) {
Assert.assertTrue("At least one player should be provided", players.length > 0);
Permanent permanent = getPermanent(attacker);
Assert.assertEquals(
"Creature should be goaded by "
+ Arrays
.stream(players)
.map(Player::getName)
.reduce((a, b) -> a + ", " + b).orElse(""),
permanent.getGoadingPlayers(),
Arrays.stream(players)
.map(TestPlayer::getId)
.collect(Collectors.toSet())
);
}
@Test
public void goadWithOwnedCreatureTest() {
// Your opponents can't cast spells during combat.
// Whenever a creature you control deals combat damage to a player, goad each creature that player controls
// (Until your next turn, that creature attacks each combat if able and attacks a player other than you if able.)
addCard(Zone.BATTLEFIELD, playerD, "Marisi, Breaker of the Coil", 1); // Creature 5/4
public void testCantAttackGoadingPlayer() {
addCard(Zone.HAND, playerA, homunculus);
addCard(Zone.BATTLEFIELD, playerA, "Island", 2);
addCard(Zone.BATTLEFIELD, playerD, lion);
addCard(Zone.BATTLEFIELD, playerC, "Abbey Griffin", 3); // Creature 2/2
addTarget(playerA, lion);
setChoice(playerA, "Yes");
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, homunculus);
attack(2, playerD, "Marisi, Breaker of the Coil", playerC);
setStopAt(3, PhaseStep.BEGIN_COMBAT);
setStrictChooseMode(true);
setStopAt(2, PhaseStep.DECLARE_BLOCKERS);
execute();
assertAllCommandsUsed();
Permanent griffinPermanent = getPermanent("Abbey Griffin");
Assert.assertFalse("Griffin can attack playerD but should not be able",
griffinPermanent.canAttack(playerD.getId(), currentGame));
Assert.assertTrue("Griffin can't attack playerA but should be able",
griffinPermanent.canAttack(playerA.getId(), currentGame));
Assert.assertTrue("Griffin can't attack playerB but should be able",
griffinPermanent.canAttack(playerB.getId(), currentGame));
assertLife(playerC, 35);
assertLife(playerD, 40); // player D can not be attacked from C because the creatures are goaded
assertLife(playerA, 40);
assertLife(playerB, 40);
assertGoaded(lion, playerA);
assertAttacking(lion, playerB, playerC);
}
/**
* In a game of commander, my opponent gained control of Marisi, Breaker of
* Coils (until end of turn) and did combat damage to another player. This
* caused the creatures damaged by Marisi's controller to be goaded.
* However, when the goaded creatures went to attack, they could not attack
* me but could attack the (former) controller of Marisi.
*/
@Test
public void goadWithNotOwnedCreatureTest() {
// Your opponents can't cast spells during combat.
// Whenever a creature you control deals combat damage to a player, goad each creature that player controls
// (Until your next turn, that creature attacks each combat if able and attacks a player other than you if able.)
addCard(Zone.BATTLEFIELD, playerA, "Marisi, Breaker of the Coil", 1); // Creature 5/4
public void testCanOnlyAttackOnePlayer() {
addCard(Zone.HAND, playerA, homunculus);
addCard(Zone.BATTLEFIELD, playerA, "Island", 2);
addCard(Zone.BATTLEFIELD, playerD, lion);
addCard(Zone.BATTLEFIELD, playerB, archon);
// Untap target creature an opponent controls and gain control of it until end of turn.
// That creature gains haste until end of turn.
// When you lose control of the creature, tap it.
addCard(Zone.HAND, playerD, "Ray of Command"); // Instant {3}{U}
addCard(Zone.BATTLEFIELD, playerD, "Island", 4);
addTarget(playerA, lion);
setChoice(playerA, "Yes");
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, homunculus);
addCard(Zone.BATTLEFIELD, playerC, "Silvercoat Lion", 1); // Creature 2/2
castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerD, "Ray of Command", "Marisi, Breaker of the Coil");
attack(2, playerD, "Marisi, Breaker of the Coil", playerC);
setStopAt(3, PhaseStep.BEGIN_COMBAT);
setStrictChooseMode(true);
setStopAt(2, PhaseStep.DECLARE_BLOCKERS);
execute();
assertAllCommandsUsed();
assertGraveyardCount(playerD, "Ray of Command", 1);
assertPermanentCount(playerA, "Marisi, Breaker of the Coil", 1);
Permanent lion = getPermanent("Silvercoat Lion", playerC);
Assert.assertFalse("Silvercoat lion shouldn't be able to attack player D but can", lion.canAttack(playerD.getId(), currentGame));
Assert.assertTrue("Silvercoat lion should be able to attack player A but can't", lion.canAttack(playerA.getId(), currentGame));
Assert.assertTrue("Silvercoat lion should be able to attack player B but can't", lion.canAttack(playerB.getId(), currentGame));
assertLife(playerD, 40);
assertLife(playerC, 35);
assertLife(playerA, 40);
assertLife(playerB, 40);
assertGoaded(lion, playerA);
assertAttacking(lion, playerC);
}
@Test
public void testMustAttackGoader() {
addCard(Zone.HAND, playerA, homunculus);
addCard(Zone.BATTLEFIELD, playerA, "Island", 2);
addCard(Zone.BATTLEFIELD, playerD, lion);
addCard(Zone.BATTLEFIELD, playerB, archon);
addCard(Zone.BATTLEFIELD, playerC, archon);
addTarget(playerA, lion);
setChoice(playerA, "Yes");
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, homunculus);
setStopAt(2, PhaseStep.DECLARE_BLOCKERS);
execute();
assertAllCommandsUsed();
assertGoaded(lion, playerA);
assertAttacking(lion, playerA);
}
@Test
public void testMultipleGoad() {
addCard(Zone.HAND, playerA, homunculus);
addCard(Zone.HAND, playerD, homunculus);
addCard(Zone.BATTLEFIELD, playerA, "Island", 2);
addCard(Zone.BATTLEFIELD, playerD, "Island", 2);
addCard(Zone.BATTLEFIELD, playerC, lion);
addTarget(playerA, lion);
setChoice(playerA, "Yes");
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, homunculus);
addTarget(playerD, lion);
setChoice(playerD, "Yes");
castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerD, homunculus);
setStopAt(3, PhaseStep.DECLARE_BLOCKERS);
execute();
assertAllCommandsUsed();
assertGoaded(lion, playerA, playerD);
assertAttacking(lion, playerB);
}
@Test
public void testMultipleGoadRestriction() {
addCard(Zone.HAND, playerA, homunculus);
addCard(Zone.HAND, playerD, homunculus);
addCard(Zone.BATTLEFIELD, playerA, "Island", 2);
addCard(Zone.BATTLEFIELD, playerD, "Island", 2);
addCard(Zone.BATTLEFIELD, playerB, archon);
addCard(Zone.BATTLEFIELD, playerC, lion);
addTarget(playerA, lion);
setChoice(playerA, "Yes");
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, homunculus);
addTarget(playerD, lion);
setChoice(playerD, "Yes");
castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerD, homunculus);
setStopAt(3, PhaseStep.DECLARE_BLOCKERS);
execute();
assertAllCommandsUsed();
assertGoaded(lion, playerA, playerD);
assertAttacking(lion, playerA, playerD);
}
@Test
public void testRegularCombatRequirement() {
addCard(Zone.BATTLEFIELD, playerA, "Berserkers of Blood Ridge");
setStopAt(1, PhaseStep.DECLARE_BLOCKERS);
execute();
assertAllCommandsUsed();
assertAttacking("Berserkers of Blood Ridge", playerB, playerC, playerD);
}
}

View file

@ -1,6 +1,5 @@
package mage.abilities.common;
import java.util.UUID;
import mage.abilities.TriggeredAbilityImpl;
import mage.abilities.effects.Effect;
import mage.constants.SetTargetPointer;
@ -11,9 +10,11 @@ import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.permanent.Permanent;
import mage.target.targetpointer.FixedTarget;
import mage.util.CardUtil;
import java.util.UUID;
/**
*
* @author LevelX2
*/
public class AttacksAllTriggeredAbility extends TriggeredAbilityImpl {
@ -74,18 +75,15 @@ public class AttacksAllTriggeredAbility extends TriggeredAbilityImpl {
return false;
}
}
getEffects().setValue("attacker", permanent);
switch (setTargetPointer) {
case PERMANENT:
for (Effect effect : getEffects()) {
effect.setTargetPointer(new FixedTarget(permanent, game));
}
getEffects().setTargetPointer(new FixedTarget(permanent, game));
break;
case PLAYER:
UUID playerId = controller ? permanent.getControllerId() : game.getCombat().getDefendingPlayerId(permanent.getId(), game);
if (playerId != null) {
for (Effect effect : getEffects()) {
effect.setTargetPointer(new FixedTarget(playerId));
}
getEffects().setTargetPointer(new FixedTarget(playerId));
}
break;
}
@ -101,10 +99,8 @@ public class AttacksAllTriggeredAbility extends TriggeredAbilityImpl {
@Override
public String getTriggerPhrase() {
return "Whenever " + (filter.getMessage().startsWith("an") ? "" : "a ")
+ filter.getMessage() + " attacks"
+ (attacksYouOrYourPlaneswalker ? " you or a planeswalker you control" : "")
+ ", " ;
return "Whenever " + CardUtil.addArticle(filter.getMessage()) + " attacks"
+ (attacksYouOrYourPlaneswalker ? " you or a planeswalker you control" : "") + ", ";
}
}

View file

@ -1,78 +0,0 @@
package mage.abilities.common;
import mage.abilities.Ability;
import mage.abilities.StaticAbility;
import mage.abilities.effects.Effect;
import mage.abilities.effects.RestrictionEffect;
import mage.abilities.effects.common.combat.AttacksIfAbleAttachedEffect;
import mage.constants.AttachmentType;
import mage.constants.Duration;
import mage.constants.Zone;
import mage.game.Game;
import mage.game.permanent.Permanent;
import java.util.UUID;
/**
* @author TheElk801
*/
public class GoadAttachedAbility extends StaticAbility {
public GoadAttachedAbility(Effect... effects) {
super(Zone.BATTLEFIELD, null);
for (Effect effect : effects) {
this.addEffect(effect);
}
this.addEffect(new AttacksIfAbleAttachedEffect(
Duration.WhileOnBattlefield, AttachmentType.AURA
).setText((getEffects().size() > 1 ? ", " : " ") + "and is goaded. <i>(It attacks each combat if able"));
this.addEffect(new GoadAttackEffect());
}
private GoadAttachedAbility(final GoadAttachedAbility ability) {
super(ability);
}
@Override
public GoadAttachedAbility copy() {
return new GoadAttachedAbility(this);
}
}
class GoadAttackEffect extends RestrictionEffect {
GoadAttackEffect() {
super(Duration.WhileOnBattlefield);
staticText = "and attacks a player other than you if able.)</i>";
}
private GoadAttackEffect(final GoadAttackEffect effect) {
super(effect);
}
@Override
public GoadAttackEffect copy() {
return new GoadAttackEffect(this);
}
@Override
public boolean applies(Permanent permanent, Ability source, Game game) {
Permanent attachment = game.getPermanent(source.getSourceId());
return attachment != null && attachment.getAttachedTo() != null
&& permanent.getId().equals(attachment.getAttachedTo());
}
@Override
public boolean canAttack(Permanent attacker, UUID defenderId, Ability source, Game game, boolean canUseChooseDialogs) {
if (defenderId == null
|| game.getState().getPlayersInRange(attacker.getControllerId(), game).size() == 2) { // just 2 players left, so it may attack you
return true;
}
// A planeswalker controlled by the controller is the defender
if (game.getPermanent(defenderId) != null) {
return !game.getPermanent(defenderId).getControllerId().equals(source.getControllerId());
}
// The controller is the defender
return !defenderId.equals(source.getControllerId());
}
}

View file

@ -1,46 +0,0 @@
package mage.abilities.effects.common.combat;
import java.util.UUID;
import mage.abilities.Ability;
import mage.abilities.effects.RestrictionEffect;
import mage.constants.Duration;
import mage.game.Game;
import mage.game.permanent.Permanent;
/**
* @author TheElk801
*/
public class CantAttackControllerDueToGoadEffect extends RestrictionEffect {
public CantAttackControllerDueToGoadEffect(Duration duration) {
super(duration);
}
public CantAttackControllerDueToGoadEffect(final CantAttackControllerDueToGoadEffect effect) {
super(effect);
}
@Override
public CantAttackControllerDueToGoadEffect copy() {
return new CantAttackControllerDueToGoadEffect(this);
}
@Override
public boolean applies(Permanent permanent, Ability source, Game game) {
return this.getTargetPointer().getTargets(game, source).contains(permanent.getId());
}
@Override
public boolean canAttack(Permanent attacker, UUID defenderId, Ability source, Game game, boolean canUseChooseDialogs) {
if (defenderId == null
|| game.getState().getPlayersInRange(attacker.getControllerId(), game).size() == 2) { // just 2 players left, so it may attack you
return true;
}
// A planeswalker controlled by the controller is the defender
if (game.getPermanent(defenderId) != null) {
return !game.getPermanent(defenderId).getControllerId().equals(source.getControllerId());
}
// The controller is the defender
return !defenderId.equals(source.getControllerId());
}
}

View file

@ -0,0 +1,44 @@
package mage.abilities.effects.common.combat;
import mage.abilities.Ability;
import mage.abilities.effects.ContinuousEffectImpl;
import mage.constants.Duration;
import mage.constants.Layer;
import mage.constants.Outcome;
import mage.constants.SubLayer;
import mage.game.Game;
import mage.game.permanent.Permanent;
/**
* @author TheElk801
*/
public class GoadAttachedEffect extends ContinuousEffectImpl {
public GoadAttachedEffect() {
super(Duration.WhileOnBattlefield, Layer.RulesEffects, SubLayer.NA, Outcome.Detriment);
staticText = "and is goaded";
}
private GoadAttachedEffect(final GoadAttachedEffect effect) {
super(effect);
}
@Override
public boolean apply(Game game, Ability source) {
Permanent permanent = source.getSourcePermanentIfItStillExists(game);
if (permanent == null) {
return false;
}
Permanent attached = game.getPermanent(permanent.getAttachedTo());
if (attached == null) {
return false;
}
attached.addGoadingPlayer(source.getControllerId());
return true;
}
@Override
public GoadAttachedEffect copy() {
return new GoadAttachedEffect(this);
}
}

View file

@ -2,19 +2,19 @@ package mage.abilities.effects.common.combat;
import mage.abilities.Ability;
import mage.abilities.Mode;
import mage.abilities.effects.ContinuousEffect;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.ContinuousEffectImpl;
import mage.constants.Duration;
import mage.constants.Layer;
import mage.constants.Outcome;
import mage.constants.SubLayer;
import mage.game.Game;
import mage.game.permanent.Permanent;
import mage.players.Player;
import mage.target.targetpointer.FixedTarget;
/**
* @author TheElk801
*/
public class GoadTargetEffect extends OneShotEffect {
public class GoadTargetEffect extends ContinuousEffectImpl {
/**
* 701.36. Goad
@ -24,10 +24,10 @@ public class GoadTargetEffect extends OneShotEffect {
* each combat if able and attacks a player other than that player if able.
*/
public GoadTargetEffect() {
super(Outcome.Detriment);
super(Duration.UntilYourNextTurn, Layer.RulesEffects, SubLayer.NA, Outcome.Detriment);
}
public GoadTargetEffect(final GoadTargetEffect effect) {
private GoadTargetEffect(final GoadTargetEffect effect) {
super(effect);
}
@ -37,26 +37,22 @@ public class GoadTargetEffect extends OneShotEffect {
}
@Override
public boolean apply(Game game, Ability source) {
public void init(Ability source, Game game) {
super.init(source, game);
Permanent targetCreature = game.getPermanent(getTargetPointer().getFirst(game, source));
Player controller = game.getPlayer(source.getControllerId());
if (targetCreature != null && controller != null) {
// TODO: Allow goad to target controller, current AttacksIfAbleTargetEffect does not support it
// https://github.com/magefree/mage/issues/5283
/*
If the creature doesnt meet any of the above exceptions and can attack, it must attack a player other than
the controller of the spell or ability that goaded it if able. If the creature cant attack any of those
players but could otherwise attack, it must attack an opposing planeswalker (controlled by any opponent)
or the player that goaded it. (2016-08-23)
*/
ContinuousEffect effect = new AttacksIfAbleTargetEffect(Duration.UntilYourNextTurn);
effect.setTargetPointer(new FixedTarget(getTargetPointer().getFirst(game, source), game));
game.addEffect(effect, source);
effect = new CantAttackControllerDueToGoadEffect(Duration.UntilYourNextTurn); // remember current controller
effect.setTargetPointer(new FixedTarget(getTargetPointer().getFirst(game, source), game));
game.addEffect(effect, source);
game.informPlayers(controller.getLogName() + " is goading " + targetCreature.getLogName());
}
}
@Override
public boolean apply(Game game, Ability source) {
Permanent targetCreature = game.getPermanent(getTargetPointer().getFirst(game, source));
if (targetCreature == null) {
return false;
}
targetCreature.addGoadingPlayer(source.getControllerId());
return true;
}

View file

@ -440,66 +440,77 @@ public class Combat implements Serializable, Copyable<Combat> {
for (Permanent creature : player.getAvailableAttackers(game)) {
boolean mustAttack = false;
Set<UUID> defendersForcedToAttack = new HashSet<>();
if (creature.getGoadingPlayers().isEmpty()) {
// check if a creature has to attack
for (Map.Entry<RequirementEffect, Set<Ability>> entry : game.getContinuousEffects().getApplicableRequirementEffects(creature, false, game).entrySet()) {
RequirementEffect effect = entry.getKey();
if (effect.mustAttack(game)
&& checkAttackRestrictions(player, game)) { // needed for Goad Effect
if (!effect.mustAttack(game)) {
continue;
}
mustAttack = true;
for (Ability ability : entry.getValue()) {
UUID defenderId = effect.mustAttackDefender(ability, game);
if (defenderId != null) {
if (defenders.contains(defenderId)) {
if (defenderId != null && defenders.contains(defenderId)) {
defendersForcedToAttack.add(defenderId);
}
}
break;
}
}
} else {
// if creature is goaded then we start with assumption that it needs to attack any player
mustAttack = true;
defendersForcedToAttack.addAll(defenders);
}
if (!mustAttack) {
continue;
}
if (mustAttack) {
// check which defenders the forced to attack creature can attack without paying a cost
Set<UUID> defendersCostlessAttackable = new HashSet<>(defenders);
for (UUID defenderId : defenders) {
if (game.getContinuousEffects().checkIfThereArePayCostToAttackBlockEffects(
new DeclareAttackerEvent(defenderId, creature.getId(), creature.getControllerId()), game)) {
new DeclareAttackerEvent(defenderId, creature.getId(), creature.getControllerId()), game
)) {
defendersCostlessAttackable.remove(defenderId);
defendersForcedToAttack.remove(defenderId);
continue;
}
for (Map.Entry<RestrictionEffect, Set<Ability>> entry : game.getContinuousEffects().getApplicableRestrictionEffects(creature, game).entrySet()) {
if (entry
.getValue()
.stream()
.anyMatch(ability -> entry.getKey().canAttack(
creature, defenderId, ability, game, false
))) {
continue;
}
defendersCostlessAttackable.remove(defenderId);
defendersForcedToAttack.remove(defenderId);
break;
}
}
// if creature can attack someone other than a player that goaded them
// then they attack one of those players, otherwise they attack any player
if (!defendersForcedToAttack.stream().allMatch(creature.getGoadingPlayers()::contains)) {
defendersForcedToAttack.removeAll(creature.getGoadingPlayers());
}
// force attack only if a defender can be attacked without paying a cost
if (!defendersCostlessAttackable.isEmpty()) {
if (defendersCostlessAttackable.isEmpty()) {
continue;
}
creaturesForcedToAttack.put(creature.getId(), defendersForcedToAttack);
// No need to attack a special defender
if (defendersForcedToAttack.isEmpty()) {
if (defendersCostlessAttackable.size() == 1) {
player.declareAttacker(creature.getId(), defendersCostlessAttackable.iterator().next(), game, false);
} else {
TargetDefender target = new TargetDefender(defendersCostlessAttackable, creature.getId());
Set<UUID> defendersToChooseFrom = defendersForcedToAttack.isEmpty() ? defendersCostlessAttackable : defendersForcedToAttack;
if (defendersToChooseFrom.size() == 1) {
player.declareAttacker(creature.getId(), defendersToChooseFrom.iterator().next(), game, false);
continue;
}
TargetDefender target = new TargetDefender(defendersToChooseFrom, creature.getId());
target.setRequired(true);
target.setTargetName("planeswalker or player for " + creature.getLogName() + " to attack (must attack effect)");
if (player.chooseTarget(Outcome.Damage, target, null, game)) {
player.declareAttacker(creature.getId(), target.getFirstTarget(), game, false);
}
}
} else {
if (defendersForcedToAttack.size() == 1) {
player.declareAttacker(creature.getId(), defendersForcedToAttack.iterator().next(), game, false);
} else {
TargetDefender target = new TargetDefender(defendersForcedToAttack, creature.getId());
target.setRequired(true);
target.setTargetName("planeswalker or player for " + creature.getLogName() + " to attack (must attack effect)");
if (player.chooseTarget(Outcome.Damage, target, null, game)) {
player.declareAttacker(creature.getId(), target.getFirstTarget(), game, false);
}
}
}
}
}
}
}
/**
@ -529,20 +540,24 @@ public class Combat implements Serializable, Copyable<Combat> {
for (Map.Entry<RestrictionEffect, Set<Ability>> entry : game.getContinuousEffects().getApplicableRestrictionEffects(attackingCreature, game).entrySet()) {
RestrictionEffect effect = entry.getKey();
for (Ability ability : entry.getValue()) {
if (!effect.canAttackCheckAfter(numberAttackers, ability, game, true)) {
if (effect.canAttackCheckAfter(numberAttackers, ability, game, true)) {
continue;
}
MageObject sourceObject = ability.getSourceObject(game);
if (attackingPlayer.isHuman()) {
attackingPlayer.resetPlayerPassedActions();
game.informPlayer(attackingPlayer, attackingCreature.getIdName() + " can't attack this way (" + (sourceObject == null ? "null" : sourceObject.getIdName()) + ')');
return false;
} else {
}
// remove attacking creatures for AI that are not allowed to attack
// can create possible not allowed attack scenarios, but not sure how to solve this
for (CombatGroup combatGroup : this.getGroups()) {
if (combatGroup.getAttackers().contains(attackingCreatureId)) {
if (this.getGroups()
.stream()
.map(CombatGroup::getAttackers)
.flatMap(Collection::stream)
.anyMatch(attackingCreatureId::equals)) {
attackerToRemove = attackingCreatureId;
}
}
check = true; // do the check again
if (numberOfChecks > 50) {
logger.error("Seems to be an AI declare attacker lock (reached 50 check iterations) " + (sourceObject == null ? "null" : sourceObject.getIdName()));
@ -553,8 +568,6 @@ public class Combat implements Serializable, Copyable<Combat> {
}
}
}
}
}
return true;
}
@ -1300,22 +1313,21 @@ public class Combat implements Serializable, Copyable<Combat> {
@SuppressWarnings("deprecation")
public boolean declareAttacker(UUID creatureId, UUID defenderId, UUID playerId, Game game) {
Permanent attacker = game.getPermanent(creatureId);
if (attacker != null) {
if (!game.replaceEvent(new DeclareAttackerEvent(defenderId, creatureId, playerId))) {
if (addAttackerToCombat(creatureId, defenderId, game)) {
if (!attacker.hasAbility(VigilanceAbility.getInstance(), game)
&& !attacker.hasAbility(JohanVigilanceAbility.getInstance(), game)) {
if (attacker == null
|| game.replaceEvent(new DeclareAttackerEvent(defenderId, creatureId, playerId))
|| !addAttackerToCombat(creatureId, defenderId, game)) {
return false;
}
if (attacker.hasAbility(VigilanceAbility.getInstance(), game)
|| attacker.hasAbility(JohanVigilanceAbility.getInstance(), game)) {
return true;
}
if (!attacker.isTapped()) {
attacker.setTapped(true);
attackersTappedByAttack.add(attacker.getId());
}
}
return true;
}
}
}
return false;
}
public boolean addAttackerToCombat(UUID attackerId, UUID defenderId, Game game) {
if (!defenders.contains(defenderId)) {

View file

@ -87,6 +87,10 @@ public interface Permanent extends Card, Controllable {
*/
boolean setClassLevel(int classLevel);
void addGoadingPlayer(UUID playerId);
Set<UUID> getGoadingPlayers();
void setCardNumber(String cid);
void setExpansionSetCode(String expansionSetCode);

View file

@ -72,6 +72,7 @@ public abstract class PermanentImpl extends CardImpl implements Permanent {
protected boolean manifested = false;
protected boolean morphed = false;
protected int classLevel = 1;
protected final Set<UUID> goadingPlayers = new HashSet<>();
protected UUID originalControllerId;
protected UUID controllerId;
protected UUID beforeResetControllerId;
@ -165,6 +166,7 @@ public abstract class PermanentImpl extends CardImpl implements Permanent {
this.monstrous = permanent.monstrous;
this.renowned = permanent.renowned;
this.classLevel = permanent.classLevel;
this.goadingPlayers.addAll(permanent.goadingPlayers);
this.pairedPermanent = permanent.pairedPermanent;
this.bandedCards.addAll(permanent.bandedCards);
this.timesLoyaltyUsed = permanent.timesLoyaltyUsed;
@ -209,6 +211,7 @@ public abstract class PermanentImpl extends CardImpl implements Permanent {
this.minBlockedBy = 1;
this.maxBlockedBy = 0;
this.copy = false;
this.goadingPlayers.clear();
}
@Override
@ -1345,14 +1348,10 @@ public abstract class PermanentImpl extends CardImpl implements Permanent {
}
//20101001 - 508.1c
if (defenderId == null) {
boolean oneCanBeAttacked = false;
for (UUID defenderToCheckId : game.getCombat().getDefenders()) {
if (canAttackCheckRestrictionEffects(defenderToCheckId, game)) {
oneCanBeAttacked = true;
break;
}
}
if (!oneCanBeAttacked) {
if (game.getCombat()
.getDefenders()
.stream()
.noneMatch(defenderToCheckId -> canAttackCheckRestrictionEffects(defenderToCheckId, game))) {
return false;
}
} else if (!canAttackCheckRestrictionEffects(defenderId, game)) {
@ -1582,6 +1581,16 @@ public abstract class PermanentImpl extends CardImpl implements Permanent {
return false;
}
@Override
public void addGoadingPlayer(UUID playerId) {
this.goadingPlayers.add(playerId);
}
@Override
public Set<UUID> getGoadingPlayers() {
return goadingPlayers;
}
@Override
public void setPairedCard(MageObjectReference pairedCard) {
this.pairedPermanent = pairedCard;

View file

@ -2625,12 +2625,11 @@ public abstract class PlayerImpl implements Player, Serializable {
Permanent attacker = game.getPermanent(attackerId);
if (attacker != null
&& attacker.canAttack(defenderId, game)
&& attacker.isControlledBy(playerId)) {
if (!game.getCombat().declareAttacker(attackerId, defenderId, playerId, game)) {
&& attacker.isControlledBy(playerId)
&& !game.getCombat().declareAttacker(attackerId, defenderId, playerId, game)) {
game.undo(playerId);
}
}
}
@Override
public void declareBlocker(UUID defenderId, UUID blockerId, UUID attackerId, Game game) {

View file

@ -38,6 +38,7 @@ public class TargetDefender extends TargetImpl {
this.filter = new FilterPlaneswalkerOrPlayer(defenders);
this.targetName = filter.getMessage();
this.attackerId = attackerId;
this.notTarget = true;
}
public TargetDefender(final TargetDefender target) {

View file

@ -3,6 +3,8 @@ package mage.util;
import java.awt.*;
import java.util.Collection;
import java.util.Random;
import java.util.Set;
import java.util.UUID;
/**
* Created by IGOUDT on 5-9-2016.