From 18663f0a7ac2b3ff127938a330600941aa2d1fef Mon Sep 17 00:00:00 2001 From: Mark Langen Date: Tue, 4 Apr 2017 00:29:54 -0600 Subject: [PATCH] Amonket Aftermath ability and card frame changes Completed * Aftermath Ability implementation complete (At least until we see comprehensive rules that contradict the way I assumed it will work) * Aftermath Card Frame rendering complete * Normal Split and Fuse Split card frame rendering complete * Amonket Split card CMC changes NOT made, but left for a separate commit --- .../card/arcane/ModernSplitCardRenderer.java | 31 +++--- Mage.Sets/src/mage/cards/d/DestinedLead.java | 101 ++++++++++++++++++ Mage.Sets/src/mage/sets/Amonkhet.java | 1 + .../abilities/keyword/AftermathAbility.java | 95 ++++++++++++++-- Mage/src/main/java/mage/cards/SplitCard.java | 21 +++- .../main/java/mage/cards/SplitCardHalf.java | 2 + .../java/mage/cards/SplitCardHalfImpl.java | 4 + .../mage/cards/mock/MockSplitCardHalf.java | 5 + .../mage/cards/repository/CardRepository.java | 2 +- .../main/java/mage/constants/CardType.java | 17 +++ .../main/java/mage/players/PlayerImpl.java | 40 ++++--- 11 files changed, 278 insertions(+), 41 deletions(-) create mode 100644 Mage.Sets/src/mage/cards/d/DestinedLead.java diff --git a/Mage.Client/src/main/java/org/mage/card/arcane/ModernSplitCardRenderer.java b/Mage.Client/src/main/java/org/mage/card/arcane/ModernSplitCardRenderer.java index a446d4818d..5e449b7702 100644 --- a/Mage.Client/src/main/java/org/mage/card/arcane/ModernSplitCardRenderer.java +++ b/Mage.Client/src/main/java/org/mage/card/arcane/ModernSplitCardRenderer.java @@ -35,9 +35,9 @@ public class ModernSplitCardRenderer extends ModernCardRenderer { private int dividerAt; private int dividerSize; - // Is fuse / consequence + // Is fuse / aftermath private boolean isFuse = false; - private boolean isConsequence = false; + private boolean isAftermath = false; public ModernSplitCardRenderer(CardView view, boolean isTransformed) { super(view, isTransformed); @@ -54,26 +54,31 @@ public class ModernSplitCardRenderer extends ModernCardRenderer { rightHalf.name = cardView.getRightSplitName(); leftHalf.name = cardView.getLeftSplitName(); - isConsequence = cardView.getName().equalsIgnoreCase("fire // ice"); for (String rule: view.getRules()) { if (rule.contains("Fuse")) { isFuse = true; break; } } + for (String rule: view.getRightSplitRules()) { + if (rule.contains("Aftermath")) { + isAftermath = true; + break; + } + } - // It's easier for rendering to swap the card halves here because for consequence cards + // It's easier for rendering to swap the card halves here because for aftermath cards // they "rotate" in opposite directions making consquence and normal split cards // have the "right" vs "left" as the top half. - if (!isConsequence()) { + if (!isAftermath()) { HalfCardProps tmp = leftHalf; leftHalf = rightHalf; rightHalf = tmp; } } - private boolean isConsequence() { - return isConsequence; + private boolean isAftermath() { + return isAftermath; } private boolean isFuse() { @@ -86,7 +91,7 @@ public class ModernSplitCardRenderer extends ModernCardRenderer { super.layout(cardWidth, cardHeight); // Decide size of divider - if (isConsequence()) { + if (isAftermath()) { dividerSize = borderWidth; dividerAt = (int)(cardHeight*0.54); } else { @@ -104,7 +109,7 @@ public class ModernSplitCardRenderer extends ModernCardRenderer { rightHalf.h = cardHeight - rightHalf.y - borderWidth*3; // Content width / height (Exchanged from width / height if the card part is rotated) - if (isConsequence()) { + if (isAftermath()) { leftHalf.cw = leftHalf.w; leftHalf.ch = leftHalf.h; } else { @@ -190,7 +195,7 @@ public class ModernSplitCardRenderer extends ModernCardRenderer { @Override protected void drawArt(Graphics2D g) { if (artImage != null && !cardView.isFaceDown()) { - if (isConsequence()) { + if (isAftermath()) { Rectangle2D topRect = new Rectangle2D.Double(0.075, 0.113, 0.832, 0.227); int topLineY = (int) (leftHalf.ch * TYPE_LINE_Y_FRAC); drawArtIntoRect(g, @@ -273,7 +278,7 @@ public class ModernSplitCardRenderer extends ModernCardRenderer { return g2; } - private Graphics2D getConsequenceHalfContext(Graphics2D g) { + private Graphics2D getAftermathHalfContext(Graphics2D g) { Graphics2D g2 = (Graphics2D)g.create(); g2.translate(rightHalf.x, rightHalf.y); g2.rotate(Math.PI / 2); @@ -299,9 +304,9 @@ public class ModernSplitCardRenderer extends ModernCardRenderer { @Override protected void drawFrame(Graphics2D g) { - if (isConsequence()) { + if (isAftermath()) { drawSplitHalfFrame(getUnmodifiedHalfContext(g), leftHalf, (int)(leftHalf.ch * TYPE_LINE_Y_FRAC)); - drawSplitHalfFrame(getConsequenceHalfContext(g), rightHalf, (rightHalf.ch - boxHeight) / 2); + drawSplitHalfFrame(getAftermathHalfContext(g), rightHalf, (rightHalf.ch - boxHeight) / 2); } else { drawSplitHalfFrame(getLeftHalfContext(g), leftHalf, (int)(leftHalf.ch * TYPE_LINE_Y_FRAC)); drawSplitHalfFrame(getRightHalfContext(g), rightHalf, (int)(rightHalf.ch * TYPE_LINE_Y_FRAC)); diff --git a/Mage.Sets/src/mage/cards/d/DestinedLead.java b/Mage.Sets/src/mage/cards/d/DestinedLead.java new file mode 100644 index 0000000000..44c708b8bc --- /dev/null +++ b/Mage.Sets/src/mage/cards/d/DestinedLead.java @@ -0,0 +1,101 @@ +/* + * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ + +package mage.cards.d; + +import mage.abilities.Ability; +import mage.abilities.effects.Effect; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.*; +import mage.abilities.effects.common.combat.MustBeBlockedByAllTargetEffect; +import mage.abilities.effects.common.continuous.BoostTargetEffect; +import mage.abilities.effects.common.continuous.GainAbilityTargetEffect; +import mage.abilities.keyword.AftermathAbility; +import mage.abilities.keyword.IndestructibleAbility; +import mage.abilities.keyword.LifelinkAbility; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.cards.SplitCard; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.Outcome; +import mage.constants.Zone; +import mage.filter.Filter; +import mage.filter.FilterCard; +import mage.filter.common.FilterCreatureCard; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.mageobject.PowerPredicate; +import mage.game.Game; +import mage.players.Player; +import mage.target.common.TargetCreaturePermanent; + +import java.util.Set; +import java.util.UUID; + +/** + * + * @author stravant + */ + + +public class DestinedLead extends SplitCard { + + public DestinedLead(UUID ownerId, CardSetInfo setInfo) { + super(ownerId,setInfo,new CardType[]{CardType.INSTANT}, new CardType[]{CardType.SORCERY},"{1}{B}","{3}{G}",false); + + // Destined + // Target creature gets +1/+0 and gains indestructible until end of turn. + getLeftHalfCard().getSpellAbility().addTarget(new TargetCreaturePermanent()); + Effect effect = new BoostTargetEffect(1, 0, Duration.EndOfTurn); + effect.setText("Target creature gets +1/+0"); + getLeftHalfCard().getSpellAbility().addEffect(effect); + + effect = new GainAbilityTargetEffect(LifelinkAbility.getInstance(), Duration.EndOfTurn); + effect.setText("and gains indestructible until end of turn"); + getLeftHalfCard().getSpellAbility().addEffect(effect); + + // to + + // Lead + // All creatures able to block target creature this turn must do so. + ((CardImpl)(getRightHalfCard())).addAbility(new AftermathAbility()); + getRightHalfCard().getSpellAbility().addTarget(new TargetCreaturePermanent()); + getRightHalfCard().getSpellAbility().addEffect(new MustBeBlockedByAllTargetEffect(Duration.EndOfTurn)); + } + + public DestinedLead(final DestinedLead card) { + super(card); + } + + @Override + public DestinedLead copy() { + return new DestinedLead(this); + } +} + diff --git a/Mage.Sets/src/mage/sets/Amonkhet.java b/Mage.Sets/src/mage/sets/Amonkhet.java index 6845178e17..d449648cde 100644 --- a/Mage.Sets/src/mage/sets/Amonkhet.java +++ b/Mage.Sets/src/mage/sets/Amonkhet.java @@ -53,5 +53,6 @@ public class Amonkhet extends ExpansionSet { this.numBoosterRare = 1; this.ratioBoosterMythic = 8; cards.add(new SetCardInfo("Dusk // Dawn", 210, Rarity.RARE, mage.cards.d.DuskDawn.class)); + cards.add(new SetCardInfo("Destined // Lead", 217, Rarity.UNCOMMON, mage.cards.d.DestinedLead.class)); } } diff --git a/Mage/src/main/java/mage/abilities/keyword/AftermathAbility.java b/Mage/src/main/java/mage/abilities/keyword/AftermathAbility.java index 1f231523b2..c704939285 100644 --- a/Mage/src/main/java/mage/abilities/keyword/AftermathAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/AftermathAbility.java @@ -27,23 +27,19 @@ */ package mage.abilities.keyword; +import mage.MageObject; import mage.abilities.Ability; -import mage.abilities.SpellAbility; import mage.abilities.common.SimpleStaticAbility; -import mage.abilities.costs.Cost; -import mage.abilities.costs.Costs; -import mage.abilities.costs.mana.ManaCost; import mage.abilities.effects.*; import mage.cards.Card; -import mage.cards.SplitCard; +import mage.cards.SplitCardHalf; +import mage.cards.SplitCardHalfImpl; import mage.constants.*; import mage.game.Game; import mage.game.events.GameEvent; import mage.game.events.ZoneChangeEvent; +import mage.game.stack.Spell; import mage.players.Player; -import mage.target.targetpointer.FixedTarget; -import org.junit.After; - import java.util.UUID; /** @@ -57,8 +53,9 @@ import java.util.UUID; */ public class AftermathAbility extends SimpleStaticAbility { public AftermathAbility() { - super(Zone.ALL, new AftermathCantCastFromHand()); - addEffect(new AftermathCastFromGraveyard()); + super(Zone.ALL, new AftermathCastFromGraveyard()); + addEffect(new AftermathCantCastFromHand()); + addEffect(new AftermathExileAsResolvesFromGraveyard()); } public AftermathAbility(final AftermathAbility ability) { @@ -101,9 +98,13 @@ class AftermathCastFromGraveyard extends AsThoughEffectImpl { return new AftermathCastFromGraveyard(this); } + private static String msb(UUID id) { + return Integer.toUnsignedString((int)id.getMostSignificantBits(), 16); + } + @Override public boolean applies(UUID objectId, Ability source, UUID affectedControllerId, Game game) { - if (objectId.equals(source.getSourceId()) && + if (objectId.equals(source.getSourceId()) & affectedControllerId.equals(source.getControllerId())) { Card card = game.getCard(source.getSourceId()); if (card != null && game.getState().getZone(source.getSourceId()) == Zone.GRAVEYARD) { @@ -151,4 +152,76 @@ class AftermathCantCastFromHand extends ContinuousRuleModifyingEffectImpl { } return false; } +} + +class AftermathExileAsResolvesFromGraveyard extends ReplacementEffectImpl { + + AftermathExileAsResolvesFromGraveyard() { + super(Duration.WhileOnStack, Outcome.Detriment); + this.staticText = "Exile it afterwards."; + } + + AftermathExileAsResolvesFromGraveyard(AftermathExileAsResolvesFromGraveyard effect) { + super(effect); + } + + @Override + public boolean checksEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.ZONE_CHANGE; + } + + @Override + public boolean applies(GameEvent evt, Ability source, Game game) { + ZoneChangeEvent event = (ZoneChangeEvent) evt; + if (event.getFromZone() == Zone.STACK && event.getToZone() != Zone.EXILED) { + // Moving something from stack to somewhere else + + // Get the source id, getting the whole split card's ID, because + // that's the card that is changing zones in the event, but + // source.getSourceId is only the split card half. + // If branch so that we also support putting Aftermath on + // non-split cards for... whatever reason, in case somebody + // wants to do that in the future. + UUID sourceId = source.getSourceId(); + Card sourceCard = game.getCard(source.getSourceId()); + if (sourceCard != null && sourceCard instanceof SplitCardHalf) { + sourceCard = ((SplitCardHalf) sourceCard).getParentCard(); + sourceId = sourceCard.getId(); + } + + if (event.getTargetId() == sourceId) { + // Moving this spell from stack to yard + Spell spell = game.getStack().getSpell(source.getSourceId()); + if (spell != null && spell.getFromZone() == Zone.GRAVEYARD) { + // And this spell was cast from the graveyard, so we need to exile it + return true; + } + } + } + return false; + } + + @Override + public boolean replaceEvent(GameEvent event, Ability source, Game game) { + UUID sourceId = source.getSourceId(); + Card sourceCard = game.getCard(source.getSourceId()); + if (sourceCard != null && sourceCard instanceof SplitCardHalf) { + sourceCard = ((SplitCardHalf) sourceCard).getParentCard(); + sourceId = sourceCard.getId(); + } + + if (sourceCard != null) { + Player player = game.getPlayer(sourceCard.getOwnerId()); + if (player != null) { + return player.moveCardToExileWithInfo(sourceCard, null, "", sourceId, game, ((ZoneChangeEvent)event).getFromZone(), true); + } + } + return false; + } + + @Override + public AftermathExileAsResolvesFromGraveyard copy() { + return new AftermathExileAsResolvesFromGraveyard(this); + } + } \ No newline at end of file diff --git a/Mage/src/main/java/mage/cards/SplitCard.java b/Mage/src/main/java/mage/cards/SplitCard.java index 8a29fad787..6785f213bb 100644 --- a/Mage/src/main/java/mage/cards/SplitCard.java +++ b/Mage/src/main/java/mage/cards/SplitCard.java @@ -28,8 +28,11 @@ package mage.cards; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; import java.util.UUID; + +import com.sun.deploy.util.ArrayUtil; import mage.abilities.Abilities; import mage.abilities.AbilitiesImpl; import mage.abilities.Ability; @@ -49,10 +52,14 @@ public abstract class SplitCard extends CardImpl { protected Card rightHalfCard; public SplitCard(UUID ownerId, CardSetInfo setInfo, CardType[] cardTypes, String costsLeft, String costsRight, boolean fused) { - super(ownerId, setInfo, cardTypes, costsLeft + costsRight, (fused ? SpellAbilityType.SPLIT_FUSED : SpellAbilityType.SPLIT)); + this(ownerId, setInfo, cardTypes, cardTypes, costsLeft, costsRight, fused); + } + + public SplitCard(UUID ownerId, CardSetInfo setInfo, CardType[] typesLeft, CardType[] typesRight, String costsLeft, String costsRight, boolean fused) { + super(ownerId, setInfo, CardType.mergeTypes(typesLeft, typesRight), costsLeft + costsRight, (fused ? SpellAbilityType.SPLIT_FUSED : SpellAbilityType.SPLIT)); String[] names = setInfo.getName().split(" // "); - leftHalfCard = new SplitCardHalfImpl(this.getOwnerId(), new CardSetInfo(names[0], setInfo.getExpansionSetCode(), setInfo.getCardNumber(), setInfo.getRarity(), setInfo.getGraphicInfo()), cardTypes, costsLeft, this, SpellAbilityType.SPLIT_LEFT); - rightHalfCard = new SplitCardHalfImpl(this.getOwnerId(), new CardSetInfo(names[1], setInfo.getExpansionSetCode(), setInfo.getCardNumber(), setInfo.getRarity(), setInfo.getGraphicInfo()), cardTypes, costsRight, this, SpellAbilityType.SPLIT_RIGHT); + leftHalfCard = new SplitCardHalfImpl(this.getOwnerId(), new CardSetInfo(names[0], setInfo.getExpansionSetCode(), setInfo.getCardNumber(), setInfo.getRarity(), setInfo.getGraphicInfo()), typesLeft, costsLeft, this, SpellAbilityType.SPLIT_LEFT); + rightHalfCard = new SplitCardHalfImpl(this.getOwnerId(), new CardSetInfo(names[1], setInfo.getExpansionSetCode(), setInfo.getCardNumber(), setInfo.getRarity(), setInfo.getGraphicInfo()), typesRight, costsRight, this, SpellAbilityType.SPLIT_RIGHT); this.splitCard = true; } @@ -139,6 +146,14 @@ public abstract class SplitCard extends CardImpl { return allAbilites; } + /** + * Currently only gets the fuse SpellAbility if there is one, but generally gets + * any abilities on a split card as a whole, and not on either half individually. + **/ + public Abilities getSharedAbilities() { + return super.getAbilities(); + } + @Override public Abilities getAbilities(Game game) { Abilities allAbilites = new AbilitiesImpl<>(); diff --git a/Mage/src/main/java/mage/cards/SplitCardHalf.java b/Mage/src/main/java/mage/cards/SplitCardHalf.java index a0593f22d5..6baee1c294 100644 --- a/Mage/src/main/java/mage/cards/SplitCardHalf.java +++ b/Mage/src/main/java/mage/cards/SplitCardHalf.java @@ -15,4 +15,6 @@ public interface SplitCardHalf extends Card { SplitCardHalf copy(); void setParentCard(SplitCard card); + + SplitCard getParentCard(); } diff --git a/Mage/src/main/java/mage/cards/SplitCardHalfImpl.java b/Mage/src/main/java/mage/cards/SplitCardHalfImpl.java index 0dcb22f2d6..bf8e5a40ee 100644 --- a/Mage/src/main/java/mage/cards/SplitCardHalfImpl.java +++ b/Mage/src/main/java/mage/cards/SplitCardHalfImpl.java @@ -82,4 +82,8 @@ public class SplitCardHalfImpl extends CardImpl implements SplitCardHalf { this.splitCardParent = card; } + @Override + public SplitCard getParentCard() { + return this.splitCardParent; + } } diff --git a/Mage/src/main/java/mage/cards/mock/MockSplitCardHalf.java b/Mage/src/main/java/mage/cards/mock/MockSplitCardHalf.java index c6d677679d..633b630bdb 100644 --- a/Mage/src/main/java/mage/cards/mock/MockSplitCardHalf.java +++ b/Mage/src/main/java/mage/cards/mock/MockSplitCardHalf.java @@ -55,4 +55,9 @@ public class MockSplitCardHalf extends MockCard implements SplitCardHalf { throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. } + @Override + public SplitCard getParentCard() { + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } + } diff --git a/Mage/src/main/java/mage/cards/repository/CardRepository.java b/Mage/src/main/java/mage/cards/repository/CardRepository.java index 8d308ecf84..1131a20ea2 100644 --- a/Mage/src/main/java/mage/cards/repository/CardRepository.java +++ b/Mage/src/main/java/mage/cards/repository/CardRepository.java @@ -61,7 +61,7 @@ public enum CardRepository { // raise this if db structure was changed private static final long CARD_DB_VERSION = 50; // raise this if new cards were added to the server - private static final long CARD_CONTENT_VERSION = 71; + private static final long CARD_CONTENT_VERSION = 74; private final TreeSet landTypes = new TreeSet<>(); private Dao cardDao; private Set classNames; diff --git a/Mage/src/main/java/mage/constants/CardType.java b/Mage/src/main/java/mage/constants/CardType.java index fbbf0f7ed8..337febbb50 100644 --- a/Mage/src/main/java/mage/constants/CardType.java +++ b/Mage/src/main/java/mage/constants/CardType.java @@ -1,5 +1,7 @@ package mage.constants; +import java.util.HashSet; + /** * * @author North @@ -26,4 +28,19 @@ public enum CardType { return text; } + /** + * Returns all of the card types from two lists of card types. + * Duplicates are eliminated. + */ + public static CardType[] mergeTypes(CardType[] a, CardType[] b) { + HashSet cardTypes = new HashSet<>(); + for (CardType t: a) { + cardTypes.add(t); + } + for (CardType t: b) { + cardTypes.add(t); + } + return cardTypes.toArray(new CardType[0]); + } + } diff --git a/Mage/src/main/java/mage/players/PlayerImpl.java b/Mage/src/main/java/mage/players/PlayerImpl.java index e22021f729..f30ed2bcbb 100644 --- a/Mage/src/main/java/mage/players/PlayerImpl.java +++ b/Mage/src/main/java/mage/players/PlayerImpl.java @@ -1245,29 +1245,30 @@ public abstract class PlayerImpl implements Player, Serializable { return useable; } - @Override - public LinkedHashMap getUseableActivatedAbilities(MageObject object, Zone zone, Game game) { - LinkedHashMap useable = new LinkedHashMap<>(); + // Get the usable activated abilities for a *single card object*, that is, either a card or half of a split card. + // Also called on the whole split card but only passing the fuse ability and other whole-split-card shared abilities + // as candidates. + private void getUseableActivatedAbilitiesHalfImpl(MageObject object, Zone zone, Game game, Abilities candidateAbilites, LinkedHashMap output) { boolean canUse = !(object instanceof Permanent) || ((Permanent) object).canUseActivatedAbilities(game); ManaOptions availableMana = null; -// ManaOptions availableMana = getManaAvailable(game); // can only be activated if mana calculation works flawless otherwise player can't play spells they could play if calculation would work correctly -// availableMana.addMana(manaPool.getMana()); - for (Ability ability : object.getAbilities()) { + // ManaOptions availableMana = getManaAvailable(game); // can only be activated if mana calculation works flawless otherwise player can't play spells they could play if calculation would work correctly + // availableMana.addMana(manaPool.getMana()); + for (Ability ability : candidateAbilites) { if (canUse || ability.getAbilityType() == AbilityType.SPECIAL_ACTION) { if (ability.getZone().match(zone)) { if (ability instanceof ActivatedAbility) { if (ability instanceof ActivatedManaAbilityImpl) { if (((ActivatedAbility) ability).canActivate(playerId, game)) { - useable.put(ability.getId(), (ActivatedAbility) ability); + output.put(ability.getId(), (ActivatedAbility) ability); } } else if (canPlay(((ActivatedAbility) ability), availableMana, object, game)) { - useable.put(ability.getId(), (ActivatedAbility) ability); + output.put(ability.getId(), (ActivatedAbility) ability); } } else if (ability instanceof AlternativeSourceCosts) { if (object.isLand()) { for (Ability ability2 : object.getAbilities().copy()) { if (ability2 instanceof PlayLandAbility) { - useable.put(ability2.getId(), (ActivatedAbility) ability2); + output.put(ability2.getId(), (ActivatedAbility) ability2); } } } @@ -1277,19 +1278,19 @@ public abstract class PlayerImpl implements Player, Serializable { } if (zone != Zone.HAND) { if (Zone.GRAVEYARD == zone && canPlayCardsFromGraveyard()) { - for (ActivatedAbility ability : object.getAbilities().getPlayableAbilities(Zone.HAND)) { + for (ActivatedAbility ability : candidateAbilites.getPlayableAbilities(Zone.HAND)) { if (canUse || ability.getAbilityType() == AbilityType.SPECIAL_ACTION) { if (ability.getManaCosts().isEmpty() && ability.getCosts().isEmpty() && ability instanceof SpellAbility) { continue; // You can't play spells from graveyard that have no costs } if (ability.canActivate(playerId, game)) { - useable.put(ability.getId(), ability); + output.put(ability.getId(), ability); } } } } if (zone != Zone.BATTLEFIELD && game.getContinuousEffects().asThough(object.getId(), AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, this.getId(), game)) { - for (Ability ability : object.getAbilities()) { + for (Ability ability : candidateAbilites) { if (canUse || ability.getAbilityType() == AbilityType.SPECIAL_ACTION) { if (ability.getManaCosts().isEmpty() && ability.getCosts().isEmpty() && ability instanceof SpellAbility && !(Objects.equals(ability.getSourceId(), getCastSourceIdWithAlternateMana()))) { continue; // You can't play spells that have no costs, unless you can play them without paying their mana costs @@ -1297,12 +1298,25 @@ public abstract class PlayerImpl implements Player, Serializable { ability.setControllerId(this.getId()); if (ability instanceof ActivatedAbility && ability.getZone().match(Zone.HAND) && ((ActivatedAbility) ability).canActivate(playerId, game)) { - useable.put(ability.getId(), (ActivatedAbility) ability); + output.put(ability.getId(), (ActivatedAbility) ability); } } } } } + } + + @Override + public LinkedHashMap getUseableActivatedAbilities(MageObject object, Zone zone, Game game) { + LinkedHashMap useable = new LinkedHashMap<>(); + if (object instanceof SplitCard) { + SplitCard splitCard = (SplitCard) object; + getUseableActivatedAbilitiesHalfImpl(splitCard.getLeftHalfCard(), zone, game, splitCard.getLeftHalfCard().getAbilities(), useable); + getUseableActivatedAbilitiesHalfImpl(splitCard.getRightHalfCard(), zone, game, splitCard.getRightHalfCard().getAbilities(), useable); + getUseableActivatedAbilitiesHalfImpl(splitCard, zone, game, splitCard.getSharedAbilities(), useable); + } else { + getUseableActivatedAbilitiesHalfImpl(object, zone, game, object.getAbilities(), useable); + } getOtherUseableActivatedAbilities(object, zone, game, useable); return useable;