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 2eb6f30a6f2..ab2ef609098 100644 --- a/forge-ai/src/main/java/forge/ai/ability/ChangeZoneAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/ChangeZoneAi.java @@ -152,7 +152,9 @@ public class ChangeZoneAi extends SpellAbilityAi { return doReturnCommanderLogic(sa, aiPlayer); } - if ("IfNotBuffed".equals(sa.getParam("AILogic"))) { + if ("Always".equals(sa.getParam("AILogic"))) { + return true; + } else if ("IfNotBuffed".equals(sa.getParam("AILogic"))) { if (ComputerUtilCard.isUselessCreature(aiPlayer, sa.getHostCard())) { return true; // debuffed by opponent's auras to the level that it becomes useless } diff --git a/forge-core/src/main/java/forge/card/CardRules.java b/forge-core/src/main/java/forge/card/CardRules.java index a16a8152179..f96411d4dc7 100644 --- a/forge-core/src/main/java/forge/card/CardRules.java +++ b/forge-core/src/main/java/forge/card/CardRules.java @@ -41,6 +41,7 @@ public final class CardRules implements ICardCharacteristics { private CardAiHints aiHints; private ColorSet colorIdentity; private String meldWith; + private String partnerWith; private CardRules(ICardFace[] faces, CardSplitType altMode, CardAiHints cah) { splitType = altMode; @@ -48,6 +49,7 @@ public final class CardRules implements ICardCharacteristics { otherPart = faces[1]; aiHints = cah; meldWith = ""; + partnerWith = ""; //calculate color identity byte colMask = calculateColorIdentity(mainPart); @@ -68,6 +70,7 @@ public final class CardRules implements ICardCharacteristics { aiHints = newRules.aiHints; colorIdentity = newRules.colorIdentity; meldWith = newRules.meldWith; + partnerWith = newRules.partnerWith; } private static byte calculateColorIdentity(final ICardFace face) { @@ -204,7 +207,7 @@ public final class CardRules implements ICardCharacteristics { } public boolean canBePartnerCommander() { - return canBeCommander() && Iterables.contains(mainPart.getKeywords(), "Partner"); + return canBeCommander() && (hasKeyword("Partner") || !this.partnerWith.isEmpty()); } public boolean canBeBrawlCommander() { @@ -216,6 +219,10 @@ public final class CardRules implements ICardCharacteristics { return meldWith; } + public String getParterWith() { + return partnerWith; + } + // vanguard card fields, they don't use sides. private int deltaHand; private int deltaLife; @@ -262,6 +269,7 @@ public final class CardRules implements ICardCharacteristics { private int curFace = 0; private CardSplitType altMode = CardSplitType.None; private String meldWith = ""; + private String partnerWith = ""; private String handLife = null; private String normalizedName = ""; @@ -291,6 +299,7 @@ public final class CardRules implements ICardCharacteristics { this.hints = null; this.has = null; this.meldWith = ""; + this.partnerWith = ""; this.normalizedName = ""; } @@ -307,6 +316,7 @@ public final class CardRules implements ICardCharacteristics { result.setNormalizedName(this.normalizedName); result.meldWith = this.meldWith; + result.partnerWith = this.partnerWith; result.setDlUrls(pictureUrl); if (StringUtils.isNotBlank(handLife)) result.setVanguardProperties(handLife); @@ -382,6 +392,9 @@ public final class CardRules implements ICardCharacteristics { case 'K': if ("K".equals(key)) { this.faces[this.curFace].addKeyword(value); + if (value.startsWith("Partner:")) { + this.partnerWith = value.split(":")[1]; + } } break; @@ -533,4 +546,8 @@ public final class CardRules implements ICardCharacteristics { return result; } + + public boolean hasKeyword(final String k) { + return Iterables.contains(mainPart.getKeywords(), k); + } } diff --git a/forge-core/src/main/java/forge/card/CardRulesPredicates.java b/forge-core/src/main/java/forge/card/CardRulesPredicates.java index cd8a3ec4e23..dba9ccad197 100644 --- a/forge-core/src/main/java/forge/card/CardRulesPredicates.java +++ b/forge-core/src/main/java/forge/card/CardRulesPredicates.java @@ -10,7 +10,6 @@ import com.google.common.collect.Iterables; import forge.util.ComparableOp; import forge.util.PredicateString; -import forge.util.PredicateString.StringOp; /** * Filtering conditions specific for CardRules class, defined here along with @@ -558,6 +557,19 @@ public final class CardRulesPredicates { } }; + public static final Predicate CAN_BE_COMMANDER = new Predicate() { + @Override + public boolean apply(final CardRules subject) { + return subject.canBeCommander(); + } + }; + public static final Predicate CAN_BE_PARTNER_COMMANDER = new Predicate() { + @Override + public boolean apply(final CardRules subject) { + return subject.canBePartnerCommander(); + } + }; + public static final Predicate IS_PLANESWALKER = CardRulesPredicates.coreType(true, CardType.CoreType.Planeswalker); public static final Predicate IS_INSTANT = CardRulesPredicates.coreType(true, CardType.CoreType.Instant); public static final Predicate IS_SORCERY = CardRulesPredicates.coreType(true, CardType.CoreType.Sorcery); @@ -570,13 +582,10 @@ public final class CardRulesPredicates { public static final Predicate IS_CONSPIRACY = CardRulesPredicates.coreType(true, CardType.CoreType.Conspiracy); public static final Predicate IS_NON_LAND = CardRulesPredicates.coreType(false, CardType.CoreType.Land); public static final Predicate IS_NON_CREATURE_SPELL = Predicates.not(Predicates.or(Presets.IS_CREATURE, Presets.IS_LAND)); - public static final Predicate CAN_BE_COMMANDER = Predicates.or(CardRulesPredicates.rules(StringOp.CONTAINS_IC, "can be your commander"), - Predicates.and(Presets.IS_CREATURE, Presets.IS_LEGENDARY)); public static final Predicate CAN_BE_BRAWL_COMMANDER = Predicates.or(Presets.IS_PLANESWALKER, Predicates.and(Presets.IS_CREATURE, Presets.IS_LEGENDARY)); /** The Constant IS_NONCREATURE_SPELL_FOR_GENERATOR. **/ - @SuppressWarnings("unchecked") public static final Predicate IS_NONCREATURE_SPELL_FOR_GENERATOR = com.google.common.base.Predicates .or(Presets.IS_SORCERY, Presets.IS_INSTANT, Presets.IS_PLANESWALKER, Presets.IS_ENCHANTMENT, Predicates.and(Presets.IS_ARTIFACT, Predicates.not(Presets.IS_CREATURE))); diff --git a/forge-core/src/main/java/forge/deck/DeckFormat.java b/forge-core/src/main/java/forge/deck/DeckFormat.java index b1494cbc322..131b4360e42 100644 --- a/forge-core/src/main/java/forge/deck/DeckFormat.java +++ b/forge-core/src/main/java/forge/deck/DeckFormat.java @@ -248,41 +248,33 @@ public enum DeckFormat { return "too many commanders"; } - // Bring values up to 100 - min++; - max++; - byte cmdCI = 0; - Boolean hasPartner = null; for (PaperCard pc : commanders) { - // For each commander decrement size by 1 (99 for 1, 98 for 2) - min--; - max--; - if (!isLegalCommander(pc.getRules())) { return "has an illegal commander"; } - - if (hasPartner != null && !hasPartner) { - return "has an illegal commander partnership"; - } - - boolean isPartner = false; - for(String s : pc.getRules().getMainPart().getKeywords()) { - if (s.equals("Partner")) { - isPartner = true; - break; - } - } - if (hasPartner == null) { - hasPartner = isPartner; - } else if (!isPartner) { - return "has an illegal commander partnership"; - } - cmdCI |= pc.getRules().getColorIdentity().getColor(); } + // special check for Partner + if (commanders.size() == 2) { + // two commander = 98 cards + min--; + max--; + + PaperCard a = commanders.get(0); + PaperCard b = commanders.get(1); + + if (a.getRules().hasKeyword("Partner") && b.getRules().hasKeyword("Partner")) { + // normal partner commander + } else if (a.getName().equals(b.getRules().getParterWith()) + && b.getName().equals(a.getRules().getParterWith())) { + // paired partner commander + } else { + return "has an illegal commander partnership"; + } + } + final List erroneousCI = new ArrayList(); Set basicLandNames = new HashSet<>(); @@ -521,6 +513,8 @@ public enum DeckFormat { for (final PaperCard p : commanders) { cmdCI |= p.getRules().getColorIdentity().getColor(); } - return Predicates.compose(Predicates.or(CardRulesPredicates.hasColorIdentity(cmdCI), CardRulesPredicates.hasKeyword("Partner")), PaperCard.FN_GET_RULES); + // TODO : check commander what kind of Partner it needs + return Predicates.compose(Predicates.or(CardRulesPredicates.hasColorIdentity(cmdCI), + CardRulesPredicates.Presets.CAN_BE_PARTNER_COMMANDER), PaperCard.FN_GET_RULES); } } 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 0974bec1015..5720840885d 100644 --- a/forge-game/src/main/java/forge/game/card/Card.java +++ b/forge-game/src/main/java/forge/game/card/Card.java @@ -1561,6 +1561,9 @@ public class Card extends GameEntity implements Comparable { || keyword.equals("Totem armor") || keyword.equals("Battle cry") || keyword.equals("Devoid")){ sbLong.append(keyword + " (" + inst.getReminderText() + ")"); + } else if (keyword.startsWith("Partner:")) { + final String[] k = keyword.split(":"); + sbLong.append("Partner with " + k[1] + " (" + inst.getReminderText() + ")"); } else if (keyword.startsWith("Modular") || keyword.startsWith("Bloodthirst") || keyword.startsWith("Fabricate") || keyword.startsWith("Soulshift") || keyword.startsWith("Crew") || keyword.startsWith("Tribute") || keyword.startsWith("Absorb") diff --git a/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java b/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java index bec2af7feef..f75817ea098 100644 --- a/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java +++ b/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java @@ -2711,6 +2711,23 @@ public class CardFactoryUtil { card.setSVar("MyriadCleanup", dbString4); inst.addTrigger(parsedTrigger); + } else if (keyword.startsWith("Partner:")) { + // Partner With + final String[] k = keyword.split(":"); + final String trigStr = "Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield " + + "| ValidCard$ Card.Self | Secondary$ True " + + "| TriggerDescription$ Partner with " + k[1] + " (" + inst.getReminderText() + ")"; + // replace , for ; in the ChangeZone + k[1] = k[1].replace(",", ";"); + + final String effect = "DB$ ChangeZone | ValidTgts$ Player | TgtPrompt$ Select target player" + + " | Origin$ Library | Destination$ Hand | ChangeType$ Card.named" + k[1] + + " | ChangeNum$ 1 | Hidden$ True | Chooser$ Targeted | Optional$ Targeted | AILogic$ Always"; + + final Trigger trigger = TriggerHandler.parseTrigger(trigStr, card, intrinsic); + trigger.setOverridingAbility(AbilityFactory.getAbility(effect, card)); + + inst.addTrigger(trigger); } else if (keyword.equals("Persist")) { final String trigStr = "Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | OncePerEffect$ True " + " | ValidCard$ Card.Self+counters_EQ0_M1M1 | Secondary$ True" + diff --git a/forge-game/src/main/java/forge/game/keyword/Keyword.java b/forge-game/src/main/java/forge/game/keyword/Keyword.java index 96cf92da283..e262519f5e6 100644 --- a/forge-game/src/main/java/forge/game/keyword/Keyword.java +++ b/forge-game/src/main/java/forge/game/keyword/Keyword.java @@ -100,7 +100,7 @@ public enum Keyword { NINJUTSU(KeywordWithCost.class, false, "%s, Return an unblocked attacker you control to hand: Put this card onto the battlefield from your hand tapped and attacking."), OUTLAST(KeywordWithCost.class, false, "%s, {T}: Put a +1/+1 counter on this creature. Outlast only as a sorcery."), OFFERING(KeywordWithType.class, false, "You may cast this card any time you could cast an instant by sacrificing a %1$s and paying the difference in mana costs between this and the sacrificed %1$s. Mana cost includes color."), - PARTNER(SimpleKeyword.class, true, "You can have two commanders if both have partner."), + PARTNER(Partner.class, true, "You can have two commanders if both have partner."), PERSIST(SimpleKeyword.class, true, "When this creature dies, if it had no -1/-1 counters on it, return it to the battlefield under its owner's control with a -1/-1 counter on it."), PHASING(SimpleKeyword.class, true, "This phases in or out before you untap during each of your untap steps. While it's phased out, it's treated as though it doesn't exist."), POISONOUS(KeywordWithAmount.class, false, "Whenever this creature deals combat damage to a player, that player gets {%d:poison counter}."), diff --git a/forge-game/src/main/java/forge/game/keyword/Partner.java b/forge-game/src/main/java/forge/game/keyword/Partner.java new file mode 100644 index 00000000000..c11b5e5c0cd --- /dev/null +++ b/forge-game/src/main/java/forge/game/keyword/Partner.java @@ -0,0 +1,35 @@ +package forge.game.keyword; + +public class Partner extends SimpleKeyword { + + private String with = null; + + public Partner() { + } + + @Override + protected void parse(String details) { + if (!details.isEmpty()) { + if (details.contains(":")) { + with = details.split(":")[1]; + } else { + with = details; + } + } + } + + @Override + protected String formatReminderText(String reminderText) { + if (with == null) { + return reminderText; + } else { + final StringBuilder sb = new StringBuilder(); + + sb.append("When this creature enters the battlefield, target player may put "); + sb.append(with); + sb.append(" into their hand from their library, then shuffle."); + + return sb.toString(); + } + } +} diff --git a/forge-gui/res/cardsfolder/upcoming/khorvath_brightflame.txt b/forge-gui/res/cardsfolder/upcoming/khorvath_brightflame.txt new file mode 100644 index 00000000000..405c6dd451f --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/khorvath_brightflame.txt @@ -0,0 +1,10 @@ +Name:Khorvath Brightflame +ManaCost:5 R +Types:Legendary Creature Dragon +PT:3/4 +K:Partner:Sylvia Brightspear:Sylvia +K:Flying +K:Haste +S:Mode$ Continuous | Affected$ Knight.YourTeamCtrl | AddKeyword$ Flying & Haste | Description$ Knights your team controls have flying and haste. +SVar:Picture:http://www.wizards.com/global/images/magic/general/khorvath_brightflame.jpg +Oracle:Partner with Sylvia Brightspear (When this creature enters the battlefield, target player may put Sylvia into their hand from their library, then shuffle.)\nFlying, haste\nKnights your team controls have flying and haste. diff --git a/forge-gui/res/cardsfolder/upcoming/sylvia_brightspear.txt b/forge-gui/res/cardsfolder/upcoming/sylvia_brightspear.txt new file mode 100644 index 00000000000..64368245c67 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/sylvia_brightspear.txt @@ -0,0 +1,9 @@ +Name:Sylvia Brightspear +ManaCost:2 W +Types:Legendary Creature Human Knight +PT:2/2 +K:Partner:Khorvath Brightflame:Khorvath +K:Double Strike +S:Mode$ Continuous | Affected$ Dragon.YourTeamCtrl | AddKeyword$ Double Strike | Description$ Dragons your team controls have double strike. +SVar:Picture:http://www.wizards.com/global/images/magic/general/sylvia_brightspear.jpg +Oracle:Partner with Khorvath Brightflame (When this creature enters the battlefield, target player may put Khorvath into their hand from their library, then shuffle.)\nDouble strike\nDragons your team controls have double strike.