diff --git a/forge-core/src/main/java/forge/card/CardAiHints.java b/forge-core/src/main/java/forge/card/CardAiHints.java index 593b7fe14b3..d039c1ff028 100644 --- a/forge-core/src/main/java/forge/card/CardAiHints.java +++ b/forge-core/src/main/java/forge/card/CardAiHints.java @@ -12,13 +12,15 @@ public class CardAiHints { private final DeckHints deckHints; private final DeckHints deckNeeds; + private final DeckHints deckHas; - public CardAiHints(boolean remAi, boolean remRandom, DeckHints dh, DeckHints dn) { + public CardAiHints(boolean remAi, boolean remRandom, DeckHints dh, DeckHints dn, DeckHints has) { isRemovedFromAIDecks = remAi; isRemovedFromRandomDecks = remRandom; deckHints = dh; deckNeeds = dn; + deckHas = has; } /** @@ -53,6 +55,13 @@ public class CardAiHints { return deckNeeds; } + /** + * @return the deckHints + */ + public DeckHints getDeckHas() { + return deckHas; + } + /** * Gets the ai status comparable. * diff --git a/forge-core/src/main/java/forge/card/CardRules.java b/forge-core/src/main/java/forge/card/CardRules.java index f49b5bdb584..7c1f27d6f5c 100644 --- a/forge-core/src/main/java/forge/card/CardRules.java +++ b/forge-core/src/main/java/forge/card/CardRules.java @@ -249,6 +249,7 @@ public final class CardRules implements ICardCharacteristics { private boolean removedFromRandomDecks = false; private DeckHints hints = null; private DeckHints needs = null; + private DeckHints has = null; /** * Reset all fields to parse next card (to avoid allocating new CardRulesReader N times) @@ -267,6 +268,7 @@ public final class CardRules implements ICardCharacteristics { this.removedFromRandomDecks = false; this.needs = null; this.hints = null; + this.has = null; } /** @@ -275,7 +277,7 @@ public final class CardRules implements ICardCharacteristics { * @return the card */ public final CardRules getCard() { - CardAiHints cah = new CardAiHints(removedFromAIDecks, removedFromRandomDecks, hints, needs ); + CardAiHints cah = new CardAiHints(removedFromAIDecks, removedFromRandomDecks, hints, needs, has); faces[0].assignMissingFields(); if (null != faces[1]) faces[1].assignMissingFields(); final CardRules result = new CardRules(faces, altMode, cah); @@ -333,6 +335,8 @@ public final class CardRules implements ICardCharacteristics { hints = new DeckHints(value); } else if ("DeckNeeds".equals(key)) { needs = new DeckHints(value); + } else if ("DeckHas".equals(key)) { + has = new DeckHints(value); } break; @@ -483,7 +487,7 @@ public final class CardRules implements ICardCharacteristics { } public static CardRules getUnsupportedCardNamed(String name) { - CardAiHints cah = new CardAiHints(true, true, null, null); + CardAiHints cah = new CardAiHints(true, true, null, null, null); CardFace[] faces = { new CardFace(name), null}; faces[0].setColor(ColorSet.fromMask(0)); faces[0].setType(CardType.parse("")); diff --git a/forge-core/src/main/java/forge/card/CardRulesPredicates.java b/forge-core/src/main/java/forge/card/CardRulesPredicates.java index 6f2cc317cdf..59b52c78413 100644 --- a/forge-core/src/main/java/forge/card/CardRulesPredicates.java +++ b/forge-core/src/main/java/forge/card/CardRulesPredicates.java @@ -183,6 +183,25 @@ public final class CardRulesPredicates { }; } + /** + * Has matching DeckHas hint. + * + * @param type + * the DeckHints.Type + * @param has + * the hint + * @return the predicate + */ + public static Predicate deckHas(final DeckHints.Type type, final String has) { + return new Predicate() { + @Override + public boolean apply(final CardRules card) { + DeckHints deckHas = card.getAiHints().getDeckHas(); + return deckHas != null && deckHas.isValid() && deckHas.contains(type, has); + } + }; + } + /** * Core type. * diff --git a/forge-core/src/main/java/forge/card/DeckHints.java b/forge-core/src/main/java/forge/card/DeckHints.java index 1f8661dc8c3..eb7a78c86a6 100644 --- a/forge-core/src/main/java/forge/card/DeckHints.java +++ b/forge-core/src/main/java/forge/card/DeckHints.java @@ -4,13 +4,14 @@ import com.google.common.base.Function; import com.google.common.base.Predicate; import com.google.common.base.Predicates; import com.google.common.collect.Iterables; -import com.google.common.collect.Lists; import forge.item.PaperCard; -import forge.util.PredicateString; import forge.util.PredicateString.StringOp; import org.apache.commons.lang3.tuple.Pair; -import java.util.*; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; /** * DeckHints provides the ability for a Card to "want" another Card or type of @@ -23,7 +24,8 @@ public class DeckHints { * Enum of types of DeckHints. */ public enum Type { - + /** The Ability */ + ABILITY, /** The Color. */ COLOR, /** The Keyword. */ @@ -64,6 +66,17 @@ public class DeckHints { public boolean isValid() { return valid; } + public boolean contains(Type type, String hint) { + if (filters == null) { + return false; + } + for (Pair filter : filters) { + if (filter.getLeft() == type && filter.getRight().equals(hint)) { + return true; + } + } + return false; + } /** * Returns a Map of Cards by Type from the given Iterable that match this @@ -129,10 +142,10 @@ public class DeckHints { private Iterable getCardsForFilter(Iterable cardList, Type type, String param) { List cards = new ArrayList<>(); switch (type) { - case TYPE: - String[] types = param.split("\\|"); - for (String t : types) { - Iterables.addAll(cards, getMatchingItems(cardList, CardRulesPredicates.subType(t), PaperCard.FN_GET_RULES)); + case ABILITY: + String[] abilities = param.split("\\|"); + for (String ability : abilities) { + Iterables.addAll(cards, getMatchingItems(cardList, CardRulesPredicates.deckHas(Type.ABILITY, ability), PaperCard.FN_GET_RULES)); } break; case COLOR: @@ -158,6 +171,12 @@ public class DeckHints { Iterables.addAll(cards, getMatchingItems(cardList, CardRulesPredicates.name(StringOp.EQUALS, name), PaperCard.FN_GET_RULES)); } break; + case TYPE: + String[] types = param.split("\\|"); + for (String t : types) { + Iterables.addAll(cards, getMatchingItems(cardList, CardRulesPredicates.subType(t), PaperCard.FN_GET_RULES)); + } + break; } return cards; } diff --git a/forge-gui-desktop/src/test/java/forge/item/DeckHintsTest.java b/forge-gui-desktop/src/test/java/forge/item/DeckHintsTest.java index 028100c1b90..1797939170b 100644 --- a/forge-gui-desktop/src/test/java/forge/item/DeckHintsTest.java +++ b/forge-gui-desktop/src/test/java/forge/item/DeckHintsTest.java @@ -131,12 +131,30 @@ public class DeckHintsTest { list.add(readCard("breaker_of_armies.txt")); list.add(readCard("benthic_infiltrator.txt")); - Map> filterByType = hints.filterByType(list); Assert.assertEquals(1, Iterables.size(filterByType.get(DeckHints.Type.KEYWORD))); Assert.assertEquals(1, Iterables.size(filterByType.get(DeckHints.Type.COLOR))); } + /** + * Test for has ability. + */ + @Test(timeOut = 1000, enabled = true) + void testDeckHasAbility() { + PaperCard pc = readCard("kozileks_channeler.txt"); + DeckHints has = pc.getRules().getAiHints().getDeckHas(); + Assert.assertNotNull(has); + + PaperCard pc2 = readCard("kozileks_pathfinder.txt"); + DeckHints hints = pc2.getRules().getAiHints().getDeckHints(); + + List list = new ArrayList<>(); + list.add(pc); + list.add(readCard("assault_griffin.txt")); + + Assert.assertEquals(1, hints.filter(list).size()); + } + /** * Create a CardPrinted from the given filename. * diff --git a/forge-gui/res/cardsfolder/b/blight_herder.txt b/forge-gui/res/cardsfolder/b/blight_herder.txt index 1af84bd9a87..cfca6ad728a 100644 --- a/forge-gui/res/cardsfolder/b/blight_herder.txt +++ b/forge-gui/res/cardsfolder/b/blight_herder.txt @@ -5,5 +5,6 @@ PT:4/5 T:Mode$ SpellCast | ValidCard$ Card.Self | OptionalDecider$ You | Execute$ TrigToken | OptionalDecider$ You | TriggerDescription$ When you cast CARDNAME, you may put two cards your opponents own from exile into their owners' graveyards. If you do, put three 1/1 colorless Eldrazi Scion creature tokens onto the battlefield. They have "Sacrifice this creature: Add 1 to your mana pool." SVar:TrigToken:AB$ Token | Cost$ ExiledMoveToGrave<2/Card.OppOwn/cards your opponents own> | TokenAmount$ 3 | TokenName$ Eldrazi Scion | TokenTypes$ Creature,Eldrazi,Scion | TokenOwner$ You | TokenColors$ Colorless | TokenPower$ 1 | TokenToughness$ 1 | TokenAltImages$ c_1_1_eldrazi_scion2,c_1_1_eldrazi_scion3 | TokenAbilities$ ABMana SVar:ABMana:AB$ Mana | Cost$ Sac<1/CARDNAME> | Produced$ C | Amount$ 1 | SpellDescription$ Add {C} to your mana pool. +DeckHints:Keyword$Ingest SVar:Picture:http://www.wizards.com/global/images/magic/general/blight_herder.jpg Oracle:When you cast Blight Herder, you may put two cards your opponents own from exile into their owners' graveyards. If you do, put three 1/1 colorless Eldrazi Scion creature tokens onto the battlefield. They have "Sacrifice this creature: Add 1 to your mana pool." diff --git a/forge-gui/res/cardsfolder/h/halimar_tidecaller.txt b/forge-gui/res/cardsfolder/h/halimar_tidecaller.txt index 208f9110a27..044425549ac 100644 --- a/forge-gui/res/cardsfolder/h/halimar_tidecaller.txt +++ b/forge-gui/res/cardsfolder/h/halimar_tidecaller.txt @@ -5,5 +5,6 @@ PT:2/3 T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigChangeZone | OptionalDecider$ You | TriggerDescription$ When CARDNAME enters the battlefield, you may return target card with awaken from your graveyard to your hand. SVar:TrigChangeZone:AB$ChangeZone | Cost$ 0 | Origin$ Graveyard | Destination$ Hand | ValidTgts$ Card.YouCtrl+withAwaken S:Mode$ Continuous | Affected$ Creature.Land+YouCtrl | AddKeyword$ Flying | Description$ Land creatures you control have flying. +DeckHints:Keyword$Awaken SVar:Picture:http://www.wizards.com/global/images/magic/general/halimar_tidecaller.jpg Oracle:When Halimar Tidecaller enters the battlefield, you may return target card with awaken from your graveyard to your hand.\nLand creatures you control have flying. diff --git a/forge-gui/res/cardsfolder/k/kozileks_channeler.txt b/forge-gui/res/cardsfolder/k/kozileks_channeler.txt index 15933820698..f3a2151f58f 100644 --- a/forge-gui/res/cardsfolder/k/kozileks_channeler.txt +++ b/forge-gui/res/cardsfolder/k/kozileks_channeler.txt @@ -3,5 +3,6 @@ ManaCost:5 Types:Creature Eldrazi PT:4/4 A:AB$ Mana | Cost$ T | Produced$ C | Amount$ 2 | SpellDescription$ Add {C}{C} to your mana pool. +DeckHas:Ability$Mana.Colorless SVar:Picture:http://www.wizards.com/global/images/magic/general/kozileks_channeler.jpg Oracle:{T}: Add {C}{C} to your mana pool. \ No newline at end of file diff --git a/forge-gui/res/cardsfolder/k/kozileks_pathfinder.txt b/forge-gui/res/cardsfolder/k/kozileks_pathfinder.txt index 6fdad414da3..3b9c2c08e65 100644 --- a/forge-gui/res/cardsfolder/k/kozileks_pathfinder.txt +++ b/forge-gui/res/cardsfolder/k/kozileks_pathfinder.txt @@ -3,5 +3,6 @@ ManaCost:6 Types:Creature Eldrazi PT:5/5 A:AB$ Pump | Cost$ C | ValidTgts$ Creature | TgtPrompt$ Select target creature | IsCurse$ True | KW$ HIDDEN CantBlockCardUIDSource | DefinedKW$ CardUIDSource | UntilHostLeavesPlayOrEOT$ True | StackDescription$ {c:Targeted} can't block CARDNAME this turn. | SpellDescription$ Target creature can't block CARDNAME this turn. +DeckHints:Ability$Mana.Colorless SVar:Picture:http://www.wizards.com/global/images/magic/general/kozileks_pathfinder.jpg Oracle:{C}: Target creature can't block Kozilek's Pathfinder this turn. ({C} represents colorless mana.) diff --git a/forge-gui/res/cardsfolder/p/processor_assault.txt b/forge-gui/res/cardsfolder/p/processor_assault.txt index 3e1aecbf896..d6d3614b6cb 100644 --- a/forge-gui/res/cardsfolder/p/processor_assault.txt +++ b/forge-gui/res/cardsfolder/p/processor_assault.txt @@ -3,5 +3,6 @@ ManaCost:1 R Types:Sorcery K:Devoid A:SP$ DealDamage | Cost$ 1 R ExiledMoveToGrave<1/Card.OppOwn/card an opponent owns> | ValidTgts$ Creature | NumDmg$ 5 | SpellDescription$ CARDNAME deals 5 damage to target creature. +DeckHints:Keyword$Ingest SVar:Picture:http://www.wizards.com/global/images/magic/general/processor_assault.jpg Oracle:Devoid (This card has no color.)\nAs an additional cost to cast Processor Assault, put a card an opponent owns from exile into that player's graveyard.\nProcessor Assault deals 5 damage to target creature. diff --git a/forge-gui/res/cardsfolder/r/ruin_processor.txt b/forge-gui/res/cardsfolder/r/ruin_processor.txt index c7637657349..6e55999a9b9 100644 --- a/forge-gui/res/cardsfolder/r/ruin_processor.txt +++ b/forge-gui/res/cardsfolder/r/ruin_processor.txt @@ -4,5 +4,6 @@ Types:Creature Eldrazi Processor PT:7/8 T:Mode$ SpellCast | ValidCard$ Card.Self | OptionalDecider$ You | Execute$ TrigHerd | TriggerDescription$ When you cast CARDNAME, you may put a card an opponent owns from exile into that player's graveyard. If you do, you gain 5 life. SVar:TrigHerd:AB$ GainLife | Cost$ ExiledMoveToGrave<1/Card.OppOwn/card an opponent owns> | Defined$ You | LifeAmount$ 5 -http://www.wizards.com/global/images/magic/general/ruin_processor.jpg +DeckHints:Keyword$Ingest +SVar:Picture:http://www.wizards.com/global/images/magic/general/ruin_processor.jpg Oracle:When you cast Ruin Processor, you may put a card an opponent owns from exile into that player's graveyard. If you do, you gain 5 life. diff --git a/forge-gui/res/cardsfolder/u/ulamogs_despoiler.txt b/forge-gui/res/cardsfolder/u/ulamogs_despoiler.txt index 0d76b5dcb41..a922d6189bd 100644 --- a/forge-gui/res/cardsfolder/u/ulamogs_despoiler.txt +++ b/forge-gui/res/cardsfolder/u/ulamogs_despoiler.txt @@ -4,5 +4,6 @@ Types:Creature Eldrazi Processor PT:5/5 T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Static$ True | Execute$ TrigPutCounters | TriggerDescription$ As CARDNAME enters the battlefield, you may put two cards your opponents own from exile into their owners' graveyards. If you do, CARDNAME enters the battlefield with four +1/+1 counters on it. SVar:TrigPutCounters:AB$ PutCounter | Cost$ ExiledMoveToGrave<2/Card.OppOwn/card an opponent owns> | Defined$ Self | CounterType$ P1P1 | CounterNum$ 4 +DeckHints:Keyword$Ingest SVar:Picture:http://www.wizards.com/global/images/magic/general/ulamogs_despoiler.jpg Oracle:As Ulamog's Despoiler enters the battlefield, you may put two cards your opponents own from exile into their owners' graveyards. If you do, Ulamog's Despoiler enters the battlefield with four +1/+1 counters on it. diff --git a/forge-gui/res/cardsfolder/v/void_attendant.txt b/forge-gui/res/cardsfolder/v/void_attendant.txt index 8a4199fb8f0..9b55c133b7a 100644 --- a/forge-gui/res/cardsfolder/v/void_attendant.txt +++ b/forge-gui/res/cardsfolder/v/void_attendant.txt @@ -5,5 +5,6 @@ PT:2/3 K:Devoid A:AB$ Token | Cost$ 1 G ExiledMoveToGrave<1/Card.OppOwn/card an opponent owns> | TokenAmount$ 1 | TokenName$ Eldrazi Scion | TokenTypes$ Creature,Eldrazi,Scion | TokenOwner$ You | TokenColors$ Colorless | TokenPower$ 1 | TokenToughness$ 1 | TokenAbilities$ ABMana | TokenAltImages$ c_1_1_eldrazi_scion2,c_1_1_eldrazi_scion3 | SpellDescription$ Put a 1/1 colorless Eldrazi Scion creature token onto the battlefield. It has "Sacrifice this creature: Add {C} to your mana pool." SVar:ABMana:AB$ Mana | Cost$ Sac<1/CARDNAME> | Produced$ C | Amount$ 1 | SpellDescription$ Add {C} to your mana pool. +DeckHints:Keyword$Ingest SVar:Picture:http://www.wizards.com/global/images/magic/general/void_attendant.jpg Oracle:Devoid (This card has no color.)\n{1}{G}, Put a card an opponent owns from exile into that player's graveyard: Put a 1/1 colorless Eldrazi Scion creature token onto the battlefield. It has "Sacrifice this creature: Add {C} to your mana pool."