diff --git a/forge-ai/src/main/java/forge/ai/ability/AttachAi.java b/forge-ai/src/main/java/forge/ai/ability/AttachAi.java
index d1947a73875..8678c600e34 100644
--- a/forge-ai/src/main/java/forge/ai/ability/AttachAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/AttachAi.java
@@ -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
diff --git a/forge-ai/src/main/java/forge/ai/ability/ChangeZoneAi.java b/forge-ai/src/main/java/forge/ai/ability/ChangeZoneAi.java
index fa364648ec1..8d3a77c2d1d 100644
--- a/forge-ai/src/main/java/forge/ai/ability/ChangeZoneAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/ChangeZoneAi.java
@@ -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;
+ }
}
}
diff --git a/forge-ai/src/main/java/forge/ai/ability/CountersPutOrRemoveAi.java b/forge-ai/src/main/java/forge/ai/ability/CountersPutOrRemoveAi.java
index 79f59f18334..89f06af68ea 100644
--- a/forge-ai/src/main/java/forge/ai/ability/CountersPutOrRemoveAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/CountersPutOrRemoveAi.java
@@ -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;
/**
*
@@ -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() {
+ @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 options, SpellAbility sa, Map 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() {
+ @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 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() {
+ @Override
+ public boolean apply(Card input) {
+ return input.ignoreLegendRule();
+ }
+ });
+
+ if (maritEmpty) {
return false;
}
} else if (type.is(CounterEnumType.M1M1) && tgt.hasKeyword(Keyword.PERSIST)) {
diff --git a/forge-ai/src/main/java/forge/ai/ability/CountersRemoveAi.java b/forge-ai/src/main/java/forge/ai/ability/CountersRemoveAi.java
index 92f636973d3..d5df83a1a60 100644
--- a/forge-ai/src/main/java/forge/ai/ability/CountersRemoveAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/CountersRemoveAi.java
@@ -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() {
+ @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));
diff --git a/forge-ai/src/main/java/forge/ai/ability/PermanentAi.java b/forge-ai/src/main/java/forge/ai/ability/PermanentAi.java
index 56b41a80289..ac6bba86efe 100644
--- a/forge-ai/src/main/java/forge/ai/ability/PermanentAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/PermanentAi.java
@@ -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;
}
}
}
diff --git a/forge-ai/src/main/java/forge/ai/ability/RepeatAi.java b/forge-ai/src/main/java/forge/ai/ability/RepeatAi.java
index f34a321b6fd..8a7a58d4951 100644
--- a/forge-ai/src/main/java/forge/ai/ability/RepeatAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/RepeatAi.java
@@ -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() {
- @Override
- public boolean apply(Card card) {
- return !card.getType().isLegendary();
- }
- })));
+ Iterable targetableAi = Iterables.filter(ai.getCreaturesInPlay(), CardPredicates.isTargetableBy(sa));
+ if (!logic.endsWith("IgnoreLegendary")) {
+ best = ComputerUtilCard.getBestAI(Iterables.filter(targetableAi, new Predicate() {
+ @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();
diff --git a/forge-ai/src/main/java/forge/ai/ability/SetStateAi.java b/forge-ai/src/main/java/forge/ai/ability/SetStateAi.java
index b010942e831..4b3451a6855 100644
--- a/forge-ai/src/main/java/forge/ai/ability/SetStateAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/SetStateAi.java
@@ -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;
diff --git a/forge-core/src/main/java/forge/card/CardDb.java b/forge-core/src/main/java/forge/card/CardDb.java
index 4838a3aa551..e0a8bbc3639 100644
--- a/forge-core/src/main/java/forge/card/CardDb.java
+++ b/forge-core/src/main/java/forge/card/CardDb.java
@@ -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 uniqueCardsByName = Maps.newTreeMap(String.CASE_INSENSITIVE_ORDER);
private final Map rulesByName;
private final Map facesByName = Maps.newTreeMap(String.CASE_INSENSITIVE_ORDER);
- private static Map artPrefs = new HashMap<>();
+ private static Map artPrefs = Maps.newHashMap();
private final Map alternateName = Maps.newTreeMap(String.CASE_INSENSITIVE_ORDER);
- private final Map artIds = new HashMap<>();
+ private final Map artIds = Maps.newHashMap();
private final CardEdition.Collection editions;
private List filtered;
+ private Map 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 getAllCards() {
return Collections.unmodifiableCollection(allCardsByName.values());
diff --git a/forge-game/src/main/java/forge/game/GameAction.java b/forge-game/src/main/java/forge/game/GameAction.java
index 897fbae53a4..57fce01e2a6 100644
--- a/forge-game/src/main/java/forge/game/GameAction.java
+++ b/forge-game/src/main/java/forge/game/GameAction.java
@@ -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 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 yamazaki = CardLists.getKeyword(a, "Legend rule doesn't apply to CARDNAME.");
- a.removeAll(yamazaki);
+ final List a = Lists.newArrayList();
- Multimap 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() {
+ @Override
+ public boolean apply(Card input) {
+ return input.hasNonLegendaryCreatureNames();
+ }
+
+ }));
+
+ Multimap uniqueLegends = Multimaps.index(a, CardPredicates.Accessors.fnGetNetName);
+ CardCollection removed = new CardCollection();
for (String name : uniqueLegends.keySet()) {
- Collection 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;
}
diff --git a/forge-game/src/main/java/forge/game/GlobalRuleChange.java b/forge-game/src/main/java/forge/game/GlobalRuleChange.java
index e0b5007432a..3e8312427e0 100644
--- a/forge-game/src/main/java/forge/game/GlobalRuleChange.java
+++ b/forge-game/src/main/java/forge/game/GlobalRuleChange.java
@@ -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."),
diff --git a/forge-game/src/main/java/forge/game/card/Card.java b/forge-game/src/main/java/forge/game/card/Card.java
index 8e8a6e0ebac..004f778d712 100644
--- a/forge-game/src/main/java/forge/game/card/Card.java
+++ b/forge-game/src/main/java/forge/game/card/Card.java
@@ -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, 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, 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, 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);
+ }
}
diff --git a/forge-game/src/main/java/forge/game/card/CardPredicates.java b/forge-game/src/main/java/forge/game/card/CardPredicates.java
index 0ad73836de9..c1c49ce427c 100644
--- a/forge-game/src/main/java/forge/game/card/CardPredicates.java
+++ b/forge-game/src/main/java/forge/game/card/CardPredicates.java
@@ -760,6 +760,13 @@ public final class CardPredicates {
return a.getCMC();
}
};
+
+ public static final Function fnGetNetName = new Function() {
+ @Override
+ public String apply(Card a) {
+ return a.getName();
+ }
+ };
}
}
diff --git a/forge-game/src/main/java/forge/game/staticability/StaticAbilityIgnoreLegendRule.java b/forge-game/src/main/java/forge/game/staticability/StaticAbilityIgnoreLegendRule.java
new file mode 100644
index 00000000000..0b7211e0d49
--- /dev/null
+++ b/forge-game/src/main/java/forge/game/staticability/StaticAbilityIgnoreLegendRule.java
@@ -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;
+ }
+}
diff --git a/forge-gui/res/cardsfolder/b/brothers_yamazaki.txt b/forge-gui/res/cardsfolder/b/brothers_yamazaki.txt
index a44e961b09c..31002da06ac 100644
--- a/forge-gui/res/cardsfolder/b/brothers_yamazaki.txt
+++ b/forge-gui/res/cardsfolder/b/brothers_yamazaki.txt
@@ -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
diff --git a/forge-gui/res/cardsfolder/m/mirror_box.txt b/forge-gui/res/cardsfolder/m/mirror_box.txt
index b54cf2f77ae..46192807181 100644
--- a/forge-gui/res/cardsfolder/m/mirror_box.txt
+++ b/forge-gui/res/cardsfolder/m/mirror_box.txt
@@ -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
diff --git a/forge-gui/res/cardsfolder/m/mirror_gallery.txt b/forge-gui/res/cardsfolder/m/mirror_gallery.txt
index 7201641b78f..6f3526a97b7 100644
--- a/forge-gui/res/cardsfolder/m/mirror_gallery.txt
+++ b/forge-gui/res/cardsfolder/m/mirror_gallery.txt
@@ -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.
diff --git a/forge-gui/res/cardsfolder/s/sakashima_of_a_thousand_faces.txt b/forge-gui/res/cardsfolder/s/sakashima_of_a_thousand_faces.txt
index d33828681bf..89e6111b219 100644
--- a/forge-gui/res/cardsfolder/s/sakashima_of_a_thousand_faces.txt
+++ b/forge-gui/res/cardsfolder/s/sakashima_of_a_thousand_faces.txt
@@ -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.)