mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-20 12:48:00 +00:00
Card: ignoreLegendRule as StaticAbility (#360)
* Card: ignoreLegendRule as StaticAbility
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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)) {
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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."),
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.)
|
||||
|
||||
Reference in New Issue
Block a user