Card: ignoreLegendRule as StaticAbility (#360)

* Card: ignoreLegendRule as StaticAbility
This commit is contained in:
Hans Mackowiak
2022-05-15 23:32:55 +02:00
committed by GitHub
parent 573e7dfb23
commit 24b2186e0d
17 changed files with 215 additions and 112 deletions

View File

@@ -22,7 +22,6 @@ import forge.ai.SpecialCardAi;
import forge.ai.SpellAbilityAi;
import forge.game.Game;
import forge.game.GameObject;
import forge.game.GlobalRuleChange;
import forge.game.ability.AbilityFactory;
import forge.game.ability.AbilityUtils;
import forge.game.ability.ApiType;
@@ -43,7 +42,6 @@ import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.player.PlayerActionConfirmMode;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.SpellPermanent;
import forge.game.spellability.TargetRestrictions;
import forge.game.staticability.StaticAbility;
import forge.game.trigger.Trigger;
@@ -78,9 +76,7 @@ public class AttachAi extends SpellAbilityAi {
}
}
if (!ai.getGame().getStaticEffects().getGlobalRuleChange(GlobalRuleChange.noLegendRule)
&& source.getType().isLegendary() && sa instanceof SpellPermanent
&& ai.isCardInPlay(source.getName())) {
if (source.isAura() && sa.isSpell() && !source.ignoreLegendRule() && ai.isCardInPlay(source.getName())) {
// Don't play the second copy of a legendary enchantment already in play
// TODO: Add some extra checks for where the AI may want to cast a replacement aura

View File

@@ -11,7 +11,6 @@ import forge.card.MagicColor;
import forge.game.Game;
import forge.game.GameEntity;
import forge.game.GameObject;
import forge.game.GlobalRuleChange;
import forge.game.ability.AbilityKey;
import forge.game.ability.AbilityUtils;
import forge.game.ability.ApiType;
@@ -276,11 +275,8 @@ public class ChangeZoneAi extends SpellAbilityAi {
//Ninjutsu
if (sa.hasParam("Ninjutsu")) {
if (source.getType().isLegendary()
&& !ai.getGame().getStaticEffects().getGlobalRuleChange(GlobalRuleChange.noLegendRule)) {
if (ai.getZone(ZoneType.Battlefield).contains(CardPredicates.nameEquals(source.getName()))) {
return false;
}
if (!source.ignoreLegendRule() && ai.isCardInPlay(source.getName())) {
return false;
}
if (ai.getGame().getPhaseHandler().getPhase().isAfter(PhaseType.COMBAT_DAMAGE)) {
return false;
@@ -732,18 +728,16 @@ public class ChangeZoneAi extends SpellAbilityAi {
}
}
// predict Legendary cards already present
if (!ai.getGame().getStaticEffects().getGlobalRuleChange(GlobalRuleChange.noLegendRule)) {
boolean nothingWillReturn = true;
for (final Card c : retrieval) {
if (!(c.getType().isLegendary() && ai.isCardInPlay(c.getName()))) {
nothingWillReturn = false;
break;
}
}
if (nothingWillReturn) {
return false;
boolean nothingWillReturn = true;
for (final Card c : retrieval) {
if (!(!c.ignoreLegendRule() && ai.isCardInPlay(c.getName()))) {
nothingWillReturn = false;
break;
}
}
if (nothingWillReturn) {
return false;
}
}
}

View File

@@ -20,11 +20,13 @@ package forge.ai.ability;
import java.util.List;
import java.util.Map;
import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilCard;
import forge.ai.SpellAbilityAi;
import forge.game.Game;
import forge.game.GlobalRuleChange;
import forge.game.card.Card;
import forge.game.card.CardCollection;
import forge.game.card.CardCollectionView;
@@ -37,6 +39,7 @@ import forge.game.player.Player;
import forge.game.player.PlayerController.BinaryChoiceType;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions;
import forge.game.zone.ZoneType;
/**
* <p>
@@ -97,13 +100,19 @@ public class CountersPutOrRemoveAi extends SpellAbilityAi {
return true;
} else {
// currently only Clockspinning
boolean noLegendary = game.getStaticEffects().getGlobalRuleChange(GlobalRuleChange.noLegendRule);
// logic to remove some counter
CardCollection countersList = CardLists.filter(list, CardPredicates.hasCounters());
if (!countersList.isEmpty()) {
if (!ai.isCardInPlay("Marit Lage") || noLegendary) {
CardCollectionView marit = ai.getCardsIn(ZoneType.Battlefield, "Marit Lage");
boolean maritEmpty = marit.isEmpty() || Iterables.contains(marit, new Predicate<Card>() {
@Override
public boolean apply(Card input) {
return input.ignoreLegendRule();
}
});
if (maritEmpty) {
CardCollectionView depthsList = CardLists.filter(countersList,
CardPredicates.nameEquals("Dark Depths"), CardPredicates.hasCounter(CounterEnumType.ICE));
@@ -198,9 +207,6 @@ public class CountersPutOrRemoveAi extends SpellAbilityAi {
@Override
public CounterType chooseCounterType(List<CounterType> options, SpellAbility sa, Map<String, Object> params) {
final Player ai = sa.getActivatingPlayer();
final Game game = ai.getGame();
boolean noLegendary = game.getStaticEffects().getGlobalRuleChange(GlobalRuleChange.noLegendRule);
Card tgt = (Card) params.get("Target");
@@ -229,7 +235,15 @@ public class CountersPutOrRemoveAi extends SpellAbilityAi {
} else {
// this counters are treat first to be removed
if ("Dark Depths".equals(tgt.getName()) && options.contains(CounterType.get(CounterEnumType.ICE))) {
if (!ai.isCardInPlay("Marit Lage") || noLegendary) {
CardCollectionView marit = ai.getCardsIn(ZoneType.Battlefield, "Marit Lage");
boolean maritEmpty = marit.isEmpty() || Iterables.contains(marit, new Predicate<Card>() {
@Override
public boolean apply(Card input) {
return input.ignoreLegendRule();
}
});
if (maritEmpty) {
return CounterType.get(CounterEnumType.ICE);
}
} else if (tgt.hasKeyword(Keyword.UNDYING) && options.contains(CounterType.get(CounterEnumType.P1P1))) {
@@ -260,12 +274,10 @@ public class CountersPutOrRemoveAi extends SpellAbilityAi {
public boolean chooseBinary(BinaryChoiceType kindOfChoice, SpellAbility sa, Map<String, Object> params) {
if (kindOfChoice.equals(BinaryChoiceType.AddOrRemove)) {
final Player ai = sa.getActivatingPlayer();
final Game game = ai.getGame();
Card tgt = (Card) params.get("Target");
CounterType type = (CounterType) params.get("CounterType");
boolean noLegendary = game.getStaticEffects().getGlobalRuleChange(GlobalRuleChange.noLegendRule);
if (tgt.getController().isOpponentOf(ai)) {
if (type.is(CounterEnumType.LOYALTY) && tgt.isPlaneswalker()) {
return false;
@@ -274,7 +286,15 @@ public class CountersPutOrRemoveAi extends SpellAbilityAi {
return ComputerUtil.isNegativeCounter(type, tgt);
} else {
if (type.is(CounterEnumType.ICE) && "Dark Depths".equals(tgt.getName())) {
if (!ai.isCardInPlay("Marit Lage") || noLegendary) {
CardCollectionView marit = ai.getCardsIn(ZoneType.Battlefield, "Marit Lage");
boolean maritEmpty = marit.isEmpty() || Iterables.contains(marit, new Predicate<Card>() {
@Override
public boolean apply(Card input) {
return input.ignoreLegendRule();
}
});
if (maritEmpty) {
return false;
}
} else if (type.is(CounterEnumType.M1M1) && tgt.hasKeyword(Keyword.PERSIST)) {

View File

@@ -3,7 +3,9 @@ package forge.ai.ability;
import java.util.List;
import java.util.Map;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.collect.Iterables;
import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilCard;
@@ -11,7 +13,6 @@ import forge.ai.ComputerUtilCost;
import forge.ai.SpellAbilityAi;
import forge.game.Game;
import forge.game.GameEntity;
import forge.game.GlobalRuleChange;
import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
import forge.game.card.CardCollection;
@@ -117,16 +118,21 @@ public class CountersRemoveAi extends SpellAbilityAi {
// Filter AI-specific targets if provided
list = ComputerUtil.filterAITgts(sa, ai, list, false);
boolean noLegendary = game.getStaticEffects().getGlobalRuleChange(GlobalRuleChange.noLegendRule);
CardCollectionView marit = ai.getCardsIn(ZoneType.Battlefield, "Marit Lage");
boolean maritEmpty = marit.isEmpty() || Iterables.contains(marit, new Predicate<Card>() {
@Override
public boolean apply(Card input) {
return input.ignoreLegendRule();
}
});
if (type.matches("All")) {
// Logic Part for Vampire Hexmage
// Break Dark Depths
if (!ai.isCardInPlay("Marit Lage") || noLegendary) {
if (maritEmpty) {
CardCollectionView depthsList = ai.getCardsIn(ZoneType.Battlefield, "Dark Depths");
depthsList = CardLists.filter(depthsList, CardPredicates.isTargetableBy(sa),
CardPredicates.hasCounter(CounterEnumType.ICE, 3));
if (!depthsList.isEmpty()) {
sa.getTargets().add(depthsList.getFirst());
return true;
@@ -161,7 +167,7 @@ public class CountersRemoveAi extends SpellAbilityAi {
}
// try to remove them from Dark Depths and Planeswalkers too
if (!ai.isCardInPlay("Marit Lage") || noLegendary) {
if (maritEmpty) {
CardCollectionView depthsList = ai.getCardsIn(ZoneType.Battlefield, "Dark Depths");
depthsList = CardLists.filter(depthsList, CardPredicates.isTargetableBy(sa),
CardPredicates.hasCounter(CounterEnumType.ICE));

View File

@@ -11,8 +11,6 @@ import forge.ai.ComputerUtilMana;
import forge.ai.SpellAbilityAi;
import forge.card.CardType.Supertype;
import forge.card.mana.ManaCost;
import forge.game.Game;
import forge.game.GlobalRuleChange;
import forge.game.card.Card;
import forge.game.card.CardCollection;
import forge.game.card.CardCollectionView;
@@ -52,28 +50,26 @@ public class PermanentAi extends SpellAbilityAi {
@Override
protected boolean checkApiLogic(final Player ai, final SpellAbility sa) {
final Card card = sa.getHostCard();
final Game game = ai.getGame();
// check on legendary
if (card.getType().isLegendary()
&& !game.getStaticEffects().getGlobalRuleChange(GlobalRuleChange.noLegendRule)) { // TODO check the risk we'd lose the effect with bad timing
if (ai.isCardInPlay(card.getName())) { // TODO check for keyword
if (!card.hasSVar("AILegendaryException")) {
// AiPlayDecision.WouldDestroyLegend
return false;
} else {
String specialRule = card.getSVar("AILegendaryException");
if ("TwoCopiesAllowed".equals(specialRule)) {
// One extra copy allowed on the battlefield, e.g. Brothers Yamazaki
if (CardLists.count(ai.getCardsIn(ZoneType.Battlefield), CardPredicates.nameEquals(card.getName())) > 1) {
return false;
}
} else if ("AlwaysAllowed".equals(specialRule)) {
// Nothing to do here, check for Legendary is disabled
} else {
// Unknown hint, assume two copies not allowed
if (!card.ignoreLegendRule() && ai.isCardInPlay(card.getName())) {
// TODO check the risk we'd lose the effect with bad timing
// TODO check for keyword
if (!card.hasSVar("AILegendaryException")) {
// AiPlayDecision.WouldDestroyLegend
return false;
} else {
String specialRule = card.getSVar("AILegendaryException");
if ("TwoCopiesAllowed".equals(specialRule)) {
// One extra copy allowed on the battlefield, e.g. Brothers Yamazaki
if (CardLists.count(ai.getCardsIn(ZoneType.Battlefield), CardPredicates.nameEquals(card.getName())) > 1) {
return false;
}
} else if ("AlwaysAllowed".equals(specialRule)) {
// Nothing to do here, check for Legendary is disabled
} else {
// Unknown hint, assume two copies not allowed
return false;
}
}
}

View File

@@ -2,10 +2,8 @@ package forge.ai.ability;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.collect.Iterables;
import forge.ai.*;
import forge.game.GlobalRuleChange;
import forge.game.card.Card;
import forge.game.card.CardPredicates;
import forge.game.phase.PhaseType;
@@ -54,16 +52,16 @@ public class RepeatAi extends SpellAbilityAi {
if (sa.usesTargeting()) {
if (logic.startsWith("CopyBestCreature")) {
Card best = null;
if (!ai.getGame().getStaticEffects().getGlobalRuleChange(GlobalRuleChange.noLegendRule) || logic.endsWith("IgnoreLegendary")) {
best = ComputerUtilCard.getBestAI(Iterables.filter(ai.getCreaturesInPlay(), Predicates.and(
CardPredicates.isTargetableBy(sa), new Predicate<Card>() {
@Override
public boolean apply(Card card) {
return !card.getType().isLegendary();
}
})));
Iterable<Card> targetableAi = Iterables.filter(ai.getCreaturesInPlay(), CardPredicates.isTargetableBy(sa));
if (!logic.endsWith("IgnoreLegendary")) {
best = ComputerUtilCard.getBestAI(Iterables.filter(targetableAi, new Predicate<Card>() {
@Override
public boolean apply(Card card) {
return card.ignoreLegendRule();
}
}));
} else {
best = ComputerUtilCard.getBestAI(Iterables.filter(ai.getCreaturesInPlay(), CardPredicates.isTargetableBy(sa)));
best = ComputerUtilCard.getBestAI(targetableAi);
}
if (best == null && mandatory && sa.canTarget(sa.getHostCard())) {
best = sa.getHostCard();

View File

@@ -9,7 +9,6 @@ import forge.ai.ComputerUtilCost;
import forge.ai.SpellAbilityAi;
import forge.card.CardStateName;
import forge.game.GlobalRuleChange;
import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
import forge.game.card.CardCollection;
@@ -238,7 +237,7 @@ public class SetStateAi extends SpellAbilityAi {
private boolean isSafeToTransformIntoLegendary(Player aiPlayer, Card source) {
// Prevent transform into legendary creature if copy already exists
// Check first if Legend Rule does still apply
if (!aiPlayer.getGame().getStaticEffects().getGlobalRuleChange(GlobalRuleChange.noLegendRule)) {
if (!source.ignoreLegendRule()) {
if (!source.hasAlternateState()) {
System.err.println("Warning: SetState without ALTERNATE on " + source.getName() + ".");
return false;

View File

@@ -20,6 +20,8 @@ package forge.card;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.collect.*;
import forge.StaticData;
import forge.card.CardEdition.CardInSet;
import forge.card.CardEdition.Type;
import forge.deck.generation.IDeckGenPool;
@@ -45,14 +47,16 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
private final Map<String, PaperCard> uniqueCardsByName = Maps.newTreeMap(String.CASE_INSENSITIVE_ORDER);
private final Map<String, CardRules> rulesByName;
private final Map<String, ICardFace> facesByName = Maps.newTreeMap(String.CASE_INSENSITIVE_ORDER);
private static Map<String, String> artPrefs = new HashMap<>();
private static Map<String, String> artPrefs = Maps.newHashMap();
private final Map<String, String> alternateName = Maps.newTreeMap(String.CASE_INSENSITIVE_ORDER);
private final Map<String, Integer> artIds = new HashMap<>();
private final Map<String, Integer> artIds = Maps.newHashMap();
private final CardEdition.Collection editions;
private List<String> filtered;
private Map<String, Boolean> nonLegendaryCreatureNames = Maps.newHashMap();
public enum CardArtPreference {
LATEST_ART_ALL_EDITIONS(false, true),
LATEST_ART_CORE_EXPANSIONS_REPRINT_ONLY(true, true),
@@ -489,7 +493,6 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
return null;
// 1. First off, try using all possible search parameters, to narrow down the actual cards looked for.
String reqEditionCode = request.edition;
PaperCard result = null;
if (reqEditionCode != null && reqEditionCode.length() > 0) {
// This get is robust even against expansion aliases (e.g. TE and TMP both valid for Tempest) -
// MOST of the extensions have two short codes, 141 out of 221 (so far)
@@ -843,6 +846,26 @@ public final class CardDb implements ICardDatabase, IDeckGenPool {
return facesByName.get(getName(name));
}
public boolean isNonLegendaryCreatureName(final String name) {
Boolean bool = nonLegendaryCreatureNames.get(name);
if (bool != null) {
return bool;
}
// check if the name is from a face
// in general token creatures does not have this
final ICardFace face = StaticData.instance().getCommonCards().getFaceByName(name);
if (face == null) {
nonLegendaryCreatureNames.put(name, false);
return false;
}
// TODO add check if face is legal in the format of the game
// name does need to be a non-legendary creature
final CardType type = face.getType();
bool = type.isCreature() && !type.isLegendary();
nonLegendaryCreatureNames.put(name, bool);
return bool;
}
@Override
public Collection<PaperCard> getAllCards() {
return Collections.unmodifiableCollection(allCardsByName.values());

View File

@@ -17,7 +17,6 @@
*/
package forge.game;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
@@ -31,12 +30,12 @@ import org.apache.commons.lang3.tuple.ImmutablePair;
import org.apache.commons.lang3.tuple.Pair;
import com.google.common.base.Predicate;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.ComparisonChain;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Multimap;
import com.google.common.collect.Multimaps;
import com.google.common.collect.Sets;
import forge.GameCommand;
@@ -1697,38 +1696,71 @@ public class GameAction {
}
private boolean handleLegendRule(Player p, CardCollection noRegCreats) {
final List<Card> a = CardLists.getType(p.getCardsIn(ZoneType.Battlefield), "Legendary");
if (a.isEmpty() || game.getStaticEffects().getGlobalRuleChange(GlobalRuleChange.noLegendRule)) {
return false;
}
boolean recheck = false;
// TODO legend rule exception into static ability
List<Card> yamazaki = CardLists.getKeyword(a, "Legend rule doesn't apply to CARDNAME.");
a.removeAll(yamazaki);
final List<Card> a = Lists.newArrayList();
Multimap<String, Card> uniqueLegends = ArrayListMultimap.create();
for (Card c : a) {
if (!c.isFaceDown()) {
uniqueLegends.put(c.getName(), c);
// check for ignore legend rule
for (Card c : CardLists.getType(p.getCardsIn(ZoneType.Battlefield), "Legendary")) {
if (!c.ignoreLegendRule()) {
a.add(c);
}
}
// TODO handle Spy Kit
if (a.isEmpty()) {
return false;
}
boolean recheck = false;
// Corner Case 1: Legendary with non legendary creature names
CardCollection nonLegendaryNames = new CardCollection(Iterables.filter(a, new Predicate<Card>() {
@Override
public boolean apply(Card input) {
return input.hasNonLegendaryCreatureNames();
}
}));
Multimap<String, Card> uniqueLegends = Multimaps.index(a, CardPredicates.Accessors.fnGetNetName);
CardCollection removed = new CardCollection();
for (String name : uniqueLegends.keySet()) {
Collection<Card> cc = uniqueLegends.get(name);
// skip the ones with empty names
if (name.isEmpty()) {
continue;
}
CardCollection cc = new CardCollection(uniqueLegends.get(name));
// check if it is a non legendary creature name
// if yes, then add the other legendary with Spy Kit too
if (!name.isEmpty() && StaticData.instance().getCommonCards().isNonLegendaryCreatureName(name)) {
cc.addAll(nonLegendaryNames);
}
if (cc.size() < 2) {
continue;
}
recheck = true;
Card toKeep = p.getController().chooseSingleEntityForEffect(new CardCollection(cc), new SpellAbility.EmptySa(ApiType.InternalLegendaryRule, new Card(-1, game), p),
Card toKeep = p.getController().chooseSingleEntityForEffect(cc, new SpellAbility.EmptySa(ApiType.InternalLegendaryRule, new Card(-1, game), p),
"You have multiple legendary permanents named \""+name+"\" in play.\n\nChoose the one to stay on battlefield (the rest will be moved to graveyard)", null);
cc.remove(toKeep);
noRegCreats.addAll(cc);
removed.addAll(cc);
}
// Corner Case 2: with all non legendary creature names
CardCollection emptyNameAllNonLegendary = new CardCollection(nonLegendaryNames);
// remove the ones that got already removed by other legend rule above
emptyNameAllNonLegendary.removeAll(removed);
if (emptyNameAllNonLegendary.size() > 1) {
recheck = true;
Card toKeep = p.getController().chooseSingleEntityForEffect(emptyNameAllNonLegendary, new SpellAbility.EmptySa(ApiType.InternalLegendaryRule, new Card(-1, game), p),
"You have multiple legendary permanents with non legendary creature names in play.\n\nChoose the one to stay on battlefield (the rest will be moved to graveyard)", null);
emptyNameAllNonLegendary.remove(toKeep);
removed.addAll(emptyNameAllNonLegendary);
}
noRegCreats.addAll(removed);
return recheck;
}

View File

@@ -28,7 +28,6 @@ public enum GlobalRuleChange {
noCreatureETBTriggers ("Creatures entering the battlefield don't cause abilities to trigger."),
noCreatureDyingTriggers ("Creatures dying don't cause abilities to trigger."),
noNight ("It can't become night."),
noLegendRule ("The legend rule doesn't apply."),
/* onlyOneAttackerATurn ("No more than one creature can attack each turn."), */
onlyOneAttackerACombat ("No more than one creature can attack each combat."),
onlyOneBlocker ("No more than one creature can block each combat."),

View File

@@ -60,6 +60,7 @@ import forge.game.staticability.StaticAbilityCantPutCounter;
import forge.game.staticability.StaticAbilityCantSacrifice;
import forge.game.staticability.StaticAbilityCantTarget;
import forge.game.staticability.StaticAbilityCantTransform;
import forge.game.staticability.StaticAbilityIgnoreLegendRule;
import forge.game.trigger.Trigger;
import forge.game.trigger.TriggerHandler;
import forge.game.trigger.TriggerType;
@@ -5189,15 +5190,9 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
} else {
// check if this card has a name from a face
// in general token creatures does not have this
final ICardFace face = StaticData.instance().getCommonCards().getFaceByName(getName());
if (face == null) {
return false;
}
// TODO add check if face is legal in the format of the game
// name does need to be a non-legendary creature
final CardType type = face.getType();
if (type != null && type.isCreature() && !type.isLegendary())
return true;
return StaticData.instance().getCommonCards().isNonLegendaryCreatureName(getName());
}
}
return sharesNameWith(c1.getName());
@@ -5220,15 +5215,9 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
if (!shares && hasNonLegendaryCreatureNames()) {
// check if the name is from a face
// in general token creatures does not have this
final ICardFace face = StaticData.instance().getCommonCards().getFaceByName(name);
if (face == null) {
return false;
}
// TODO add check if face is legal in the format of the game
// name does need to be a non-legendary creature
final CardType type = face.getType();
if (type.isCreature() && !type.isLegendary())
return true;
return StaticData.instance().getCommonCards().isNonLegendaryCreatureName(name);
}
return shares;
}
@@ -7107,4 +7096,16 @@ public class Card extends GameEntity implements Comparable<Card>, IHasSVars {
}
return getGame().getCombat().isAttacking(this);
}
public boolean ignoreLegendRule() {
// not legendary
if (!getType().isLegendary()) {
return true;
}
// empty name and no "has non legendary creature names"
if (this.getName().isEmpty() && !hasNonLegendaryCreatureNames()) {
return true;
}
return StaticAbilityIgnoreLegendRule.ignoreLegendRule(this);
}
}

View File

@@ -760,6 +760,13 @@ public final class CardPredicates {
return a.getCMC();
}
};
public static final Function<Card, String> fnGetNetName = new Function<Card, String>() {
@Override
public String apply(Card a) {
return a.getName();
}
};
}
}

View File

@@ -0,0 +1,33 @@
package forge.game.staticability;
import forge.game.Game;
import forge.game.card.Card;
import forge.game.zone.ZoneType;
public class StaticAbilityIgnoreLegendRule {
static String MODE = "IgnoreLegendRule";
public static boolean ignoreLegendRule(final Card card) {
final Game game = card.getGame();
for (final Card ca : game.getCardsIn(ZoneType.STATIC_ABILITIES_SOURCE_ZONES)) {
for (final StaticAbility stAb : ca.getStaticAbilities()) {
if (!stAb.getParam("Mode").equals(MODE) || stAb.isSuppressed() || !stAb.checkConditions()) {
continue;
}
if (applyIgnoreLegendRuleAbility(stAb, card)) {
return true;
}
}
}
return false;
}
public static boolean applyIgnoreLegendRuleAbility(final StaticAbility stAb, final Card card) {
if (!stAb.matchesValidParam("ValidCard", card)) {
return false;
}
return true;
}
}

View File

@@ -3,8 +3,7 @@ ManaCost:2 R
Types:Legendary Creature Human Samurai
PT:2/1
K:Bushido:1
S:Mode$ Continuous | Affected$ Permanent.namedBrothers Yamazaki | CheckSVar$ X | SVarCompare$ EQ2 | AddHiddenKeyword$ Legend rule doesn't apply to CARDNAME. | Description$ If there are exactly two permanents named Brothers Yamazaki on the battlefield, the "legend rule" doesn't apply to them.
SVar:X:Count$Valid Permanent.namedBrothers Yamazaki
S:Mode$ IgnoreLegendRule | ValidCard$ Permanent.namedBrothers Yamazaki | IsPresent$ Permanent.namedBrothers Yamazaki | PresentCompare$ EQ2 | Description$ If there are exactly two permanents named Brothers Yamazaki on the battlefield, the "legend rule" doesn't apply to them.
S:Mode$ Continuous | Affected$ Creature.Other+namedBrothers Yamazaki | AddPower$ 2 | AddToughness$ 2 | AddKeyword$ Haste | Description$ Each other creature named CARDNAME gets +2/+2 and has haste.
DeckHints:Name$Brothers Yamazaki
SVar:AILegendaryException:TwoCopiesAllowed

View File

@@ -1,7 +1,7 @@
Name:Mirror Box
ManaCost:3
Types:Artifact
S:Mode$ Continuous | Affected$ Permanent.YouCtrl | AddHiddenKeyword$ Legend rule doesn't apply to CARDNAME. | Description$ The "legend rule" doesn't apply to permanents you control.
S:Mode$ IgnoreLegendRule | ValidCard$ Permanent.YouCtrl | Description$ The "legend rule" doesn't apply to permanents you control.
S:Mode$ Continuous | Affected$ Creature.Legendary+YouCtrl | AddPower$ 1 | AddToughness$ 1 | Description$ Each legendary creature you control gets +1/+1.
S:Mode$ Continuous | Affected$ Creature.nonToken+YouCtrl | AddPower$ AffectedX | AddToughness$ AffectedX | Description$ Each nontoken creature you control gets +1/+1 for each other creature you control with the same name as that creature.
SVar:AffectedX:Count$Valid Creature.Other+sharesNameWith+YouCtrl

View File

@@ -1,7 +1,7 @@
Name:Mirror Gallery
ManaCost:5
Types:Artifact
S:Mode$ Continuous | GlobalRule$ The legend rule doesn't apply. | Description$ The "legend rule" doesn't apply.
S:Mode$ IgnoreLegendRule | Description$ The "legend rule" doesn't apply.
SVar:NonStackingEffect:True
AI:RemoveDeck:Random
Oracle:The "legend rule" doesn't apply.

View File

@@ -4,7 +4,7 @@ Types:Legendary Creature Human Rogue
PT:3/1
K:ETBReplacement:Copy:DBCopy:Optional
SVar:DBCopy:DB$ Clone | Choices$ Creature.YouCtrl+Other | AddKeywords$ Partner | AddStaticAbilities$ NoLegendRule | SpellDescription$ You may have CARDNAME enter the battlefield as a copy of another creature you control, except it has CARDNAME's other abilities.
SVar:NoLegendRule:Mode$ Continuous | Affected$ Permanent.YouCtrl | AddHiddenKeyword$ Legend rule doesn't apply to CARDNAME. | Description$ The "legend rule" doesn't apply to permanents you control.
S:Mode$ Continuous | Affected$ Permanent.YouCtrl | AddHiddenKeyword$ Legend rule doesn't apply to CARDNAME. | Description$ The "legend rule" doesn't apply to permanents you control.
SVar:NoLegendRule:Mode$ IgnoreLegendRule | ValidCard$ Permanent.YouCtrl | Description$ The "legend rule" doesn't apply to permanents you control.
S:Mode$ IgnoreLegendRule | ValidCard$ Permanent.YouCtrl | Description$ The "legend rule" doesn't apply to permanents you control.
K:Partner
Oracle:You may have Sakashima of a Thousand Faces enter the battlefield as a copy of another creature you control, except it has Sakashima of a Thousand Faces's other abilities.\nThe "legend rule" doesn't apply to permanents you control.\nPartner (You can have two commanders if both have partner.)