From 648184248233a4fff5a5b7ae254450c4f81728ef Mon Sep 17 00:00:00 2001 From: Hanmac Date: Sun, 27 May 2018 13:42:16 +0200 Subject: [PATCH 1/5] Partner with: added Logic into CardRules and added Trigger --- .../src/main/java/forge/card/CardRules.java | 19 +++++++- .../java/forge/card/CardRulesPredicates.java | 11 +++-- .../src/main/java/forge/deck/DeckFormat.java | 46 ++++++++----------- .../src/main/java/forge/game/card/Card.java | 3 ++ .../java/forge/game/card/CardFactoryUtil.java | 17 +++++++ .../main/java/forge/game/keyword/Keyword.java | 2 +- .../main/java/forge/game/keyword/Partner.java | 35 ++++++++++++++ 7 files changed, 100 insertions(+), 33 deletions(-) create mode 100644 forge-game/src/main/java/forge/game/keyword/Partner.java 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..b47eb9ae67e 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,13 @@ 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 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 +576,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..f4afb99c0ad 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<>(); 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..d2d8997c91b 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].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"; + + 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(); + } + } +} From 3c1edcef4e9acf30bb6d04d0e509e8166b4f9b66 Mon Sep 17 00:00:00 2001 From: Hanmac Date: Sun, 27 May 2018 13:42:48 +0200 Subject: [PATCH 2/5] cards: added Sylvia and Khorvath as Partner --- .../res/cardsfolder/upcoming/khorvath_brightflame.txt | 10 ++++++++++ .../res/cardsfolder/upcoming/sylvia_brightspear.txt | 9 +++++++++ 2 files changed, 19 insertions(+) create mode 100644 forge-gui/res/cardsfolder/upcoming/khorvath_brightflame.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/sylvia_brightspear.txt 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. From ef9603755511d98898396eb5b8987076a72616f0 Mon Sep 17 00:00:00 2001 From: Hanmac Date: Sun, 27 May 2018 14:56:45 +0200 Subject: [PATCH 3/5] Partner with: fixed , to ; replace --- forge-game/src/main/java/forge/game/card/CardFactoryUtil.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 d2d8997c91b..3e085aaad31 100644 --- a/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java +++ b/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java @@ -2718,7 +2718,7 @@ public class CardFactoryUtil { "| ValidCard$ Card.Self | Secondary$ True " + "| TriggerDescription$ Partner with " + k[1] + " (" + inst.getReminderText() + ")"; // replace , for ; in the ChangeZone - k[1].replace(",", ";"); + 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] + From e031ac8176374f61d524a4b37e48bcff73fdcab4 Mon Sep 17 00:00:00 2001 From: Hanmac Date: Sun, 27 May 2018 15:52:18 +0200 Subject: [PATCH 4/5] PartnerWith: extended DeckFormat Predicate --- .../src/main/java/forge/card/CardRulesPredicates.java | 6 ++++++ forge-core/src/main/java/forge/deck/DeckFormat.java | 4 +++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/forge-core/src/main/java/forge/card/CardRulesPredicates.java b/forge-core/src/main/java/forge/card/CardRulesPredicates.java index b47eb9ae67e..dba9ccad197 100644 --- a/forge-core/src/main/java/forge/card/CardRulesPredicates.java +++ b/forge-core/src/main/java/forge/card/CardRulesPredicates.java @@ -563,6 +563,12 @@ public final class CardRulesPredicates { 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); diff --git a/forge-core/src/main/java/forge/deck/DeckFormat.java b/forge-core/src/main/java/forge/deck/DeckFormat.java index f4afb99c0ad..131b4360e42 100644 --- a/forge-core/src/main/java/forge/deck/DeckFormat.java +++ b/forge-core/src/main/java/forge/deck/DeckFormat.java @@ -513,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); } } From 053726519c929029df1ad0cf4f04c6a24d7f10fa Mon Sep 17 00:00:00 2001 From: Agetian Date: Sun, 27 May 2018 17:29:57 +0300 Subject: [PATCH 5/5] - Simple AI logic extension to make the Partner With commanders AI-playable. --- forge-ai/src/main/java/forge/ai/ability/ChangeZoneAi.java | 4 +++- forge-game/src/main/java/forge/game/card/CardFactoryUtil.java | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) 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-game/src/main/java/forge/game/card/CardFactoryUtil.java b/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java index 3e085aaad31..f75817ea098 100644 --- a/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java +++ b/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java @@ -2722,7 +2722,7 @@ public class CardFactoryUtil { 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"; + " | ChangeNum$ 1 | Hidden$ True | Chooser$ Targeted | Optional$ Targeted | AILogic$ Always"; final Trigger trigger = TriggerHandler.parseTrigger(trigStr, card, intrinsic); trigger.setOverridingAbility(AbilityFactory.getAbility(effect, card));