* Bolas's Citadel - fixed that it can't play cards with cycling (#6215);

This commit is contained in:
Oleg Agafonov 2020-01-22 14:45:44 +04:00
parent cf759e0443
commit c835fb409d
4 changed files with 115 additions and 33 deletions

View file

@ -67,7 +67,6 @@ public class BolassCitadelTest extends CardTestPlayerBase {
addCard(Zone.LIBRARY, playerA, "Fellwar Stone"); addCard(Zone.LIBRARY, playerA, "Fellwar Stone");
// cast from top library for 2 life // cast from top library for 2 life
//showAvaileableAbilities("before", 1, PhaseStep.PRECOMBAT_MAIN, playerA);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Fellwar Stone"); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Fellwar Stone");
setStrictChooseMode(true); setStrictChooseMode(true);
@ -78,4 +77,56 @@ public class BolassCitadelTest extends CardTestPlayerBase {
assertPermanentCount(playerA, "Fellwar Stone", 1); assertPermanentCount(playerA, "Fellwar Stone", 1);
assertLife(playerA, 20 - 2); assertLife(playerA, 20 - 2);
} }
@Test
public void testCardWithCycling() {
// bug: can't cast cards with cycling, see https://github.com/magefree/mage/issues/6215
// bug active only in GUI (HumanPlayer)
removeAllCardsFromLibrary(playerA);
// You may play the top card of your library. If you cast a spell this way, pay life equal to its converted mana cost rather than pay its mana cost.
addCard(Zone.BATTLEFIELD, playerA, "Bolas's Citadel");
// Cycling {2} ({2}, Discard this card: Draw a card.)
addCard(Zone.LIBRARY, playerA, "Archfiend of Ifnir"); // {3}{B}{B}
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Archfiend of Ifnir");
setStrictChooseMode(true);
setStopAt(1, PhaseStep.POSTCOMBAT_MAIN);
execute();
assertAllCommandsUsed();
assertPermanentCount(playerA, "Archfiend of Ifnir", 1);
assertLife(playerA, 20 - 5);
}
@Test
public void testCardWithAdventure() {
removeAllCardsFromLibrary(playerA);
// You may play the top card of your library. If you cast a spell this way, pay life equal to its converted mana cost rather than pay its mana cost.
addCard(Zone.BATTLEFIELD, playerA, "Bolas's Citadel");
// Adventure spell: Chop Down {2}{W} - Destroy target creature with power 4 or greater.
addCard(Zone.LIBRARY, playerA, "Giant Killer"); // {W}
addCard(Zone.BATTLEFIELD, playerA, "Ferocious Zheng"); // 4/4
addCard(Zone.BATTLEFIELD, playerA, "Plains", 1);
// cast adventure
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Chop Down", "Ferocious Zheng");
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN);
checkPermanentCount("must kill", 1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Ferocious Zheng", 0);
checkExileCount("on adventure", 1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Giant Killer", 1);
// cast normal
castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Giant Killer");
setStrictChooseMode(true);
setStopAt(1, PhaseStep.POSTCOMBAT_MAIN);
execute();
assertAllCommandsUsed();
assertPermanentCount(playerA, "Giant Killer", 1);
assertGraveyardCount(playerA, "Ferocious Zheng", 1);
assertLife(playerA, 20 - 3);
}
} }

View file

@ -1,12 +1,11 @@
package mage.abilities.effects; package mage.abilities.effects;
import java.io.Serializable;
import java.util.*;
import java.util.Map.Entry;
import java.util.stream.Collectors;
import mage.MageObject; import mage.MageObject;
import mage.MageObjectReference; import mage.MageObjectReference;
import mage.abilities.*; import mage.abilities.Ability;
import mage.abilities.MageSingleton;
import mage.abilities.SpellAbility;
import mage.abilities.StaticAbility;
import mage.abilities.effects.common.continuous.BecomesFaceDownCreatureEffect; import mage.abilities.effects.common.continuous.BecomesFaceDownCreatureEffect;
import mage.abilities.effects.common.continuous.CommanderReplacementEffect; import mage.abilities.effects.common.continuous.CommanderReplacementEffect;
import mage.cards.*; import mage.cards.*;
@ -27,6 +26,11 @@ import mage.players.Player;
import mage.target.common.TargetCardInHand; import mage.target.common.TargetCardInHand;
import org.apache.log4j.Logger; import org.apache.log4j.Logger;
import java.io.Serializable;
import java.util.*;
import java.util.Map.Entry;
import java.util.stream.Collectors;
/** /**
* @author BetaSteward_at_googlemail.com * @author BetaSteward_at_googlemail.com
*/ */
@ -507,8 +511,7 @@ public class ContinuousEffects implements Serializable {
if (affectedAbility != null && affectedAbility.getSourceObject(game) instanceof SplitCardHalf) { if (affectedAbility != null && affectedAbility.getSourceObject(game) instanceof SplitCardHalf) {
idToCheck = ((SplitCardHalf) affectedAbility.getSourceObject(game)).getParentCard().getId(); idToCheck = ((SplitCardHalf) affectedAbility.getSourceObject(game)).getParentCard().getId();
} else if (affectedAbility != null && affectedAbility.getSourceObject(game) instanceof AdventureCardSpell } else if (affectedAbility != null && affectedAbility.getSourceObject(game) instanceof AdventureCardSpell
&& type != AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE && !type.needPlayCardAbility()) {
&& type != AsThoughEffectType.CAST_AS_INSTANT) {
// adventure spell uses alternative characteristics for spell/stack // adventure spell uses alternative characteristics for spell/stack
idToCheck = ((AdventureCardSpell) affectedAbility.getSourceObject(game)).getParentCard().getId(); idToCheck = ((AdventureCardSpell) affectedAbility.getSourceObject(game)).getParentCard().getId();
} else { } else {
@ -516,27 +519,37 @@ public class ContinuousEffects implements Serializable {
if (card instanceof SplitCardHalf) { if (card instanceof SplitCardHalf) {
idToCheck = ((SplitCardHalf) card).getParentCard().getId(); idToCheck = ((SplitCardHalf) card).getParentCard().getId();
} else if (card instanceof AdventureCardSpell } else if (card instanceof AdventureCardSpell
&& type != AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE && !type.needPlayCardAbility()) {
&& type != AsThoughEffectType.CAST_AS_INSTANT) {
// adventure spell uses alternative characteristics for spell/stack // adventure spell uses alternative characteristics for spell/stack
idToCheck = ((AdventureCardSpell) card).getParentCard().getId(); idToCheck = ((AdventureCardSpell) card).getParentCard().getId();
} else { } else {
idToCheck = objectId; idToCheck = objectId;
} }
} }
for (AsThoughEffect effect : asThoughEffectsList) { for (AsThoughEffect effect : asThoughEffectsList) {
Set<Ability> abilities = asThoughEffectsMap.get(type).getAbility(effect.getId()); Set<Ability> abilities = asThoughEffectsMap.get(type).getAbility(effect.getId());
for (Ability ability : abilities) { for (Ability ability : abilities) {
if (affectedAbility == null) { if (affectedAbility == null) {
// applies to own ability (one effect can be used in multiple abilities)
if (effect.applies(idToCheck, ability, controllerId, game)) { if (effect.applies(idToCheck, ability, controllerId, game)) {
return new MageObjectReference(ability.getSourceObject(game), game); return new MageObjectReference(ability.getSourceObject(game), game);
} }
} else if (effect.applies(idToCheck, affectedAbility, ability, game, controllerId)) { } else {
// applies to affected ability
// filter play abilities (no need to check it in every effect's code)
if (type.needPlayCardAbility() && !affectedAbility.getAbilityType().isPlayCardAbility()) {
continue;
}
if (effect.applies(idToCheck, affectedAbility, ability, game, controllerId)) {
return new MageObjectReference(ability.getSourceObject(game), game); return new MageObjectReference(ability.getSourceObject(game), game);
} }
} }
} }
} }
}
return null; return null;
} }

View file

@ -1,29 +1,33 @@
package mage.constants; package mage.constants;
/** /**
*
* @author North * @author North
*/ */
public enum AbilityType { public enum AbilityType {
PLAY_LAND("Play land", true),
PLAY_LAND("Play land"), MANA("Mana", false),
MANA("Mana"), SPELL("Spell", true),
SPELL("Spell"), ACTIVATED("Activated", false),
ACTIVATED("Activated"), STATIC("Static", false),
STATIC("Static"), TRIGGERED("Triggered", false),
TRIGGERED("Triggered"), EVASION("Evasion", false),
EVASION("Evasion"), LOYALTY("Loyalty", false),
LOYALTY("Loyalty"), SPECIAL_ACTION("Special Action", false);
SPECIAL_ACTION("Special Action");
private final String text; private final String text;
private final boolean playCardAbility;
AbilityType(String text) { AbilityType(String text, boolean playCardAbility) {
this.text = text; this.text = text;
this.playCardAbility = playCardAbility;
} }
@Override @Override
public String toString() { public String toString() {
return text; return text;
} }
public boolean isPlayCardAbility() {
return playCardAbility;
}
} }

View file

@ -24,8 +24,8 @@ public enum AsThoughEffectType {
// 2. All effects in "applies" must checks affectedControllerId.equals(source.getControllerId()) (if not then all players will be able to play it) // 2. All effects in "applies" must checks affectedControllerId.equals(source.getControllerId()) (if not then all players will be able to play it)
// 3. Target points to mainCard, but card's characteristics from objectId (split, adventure) // 3. Target points to mainCard, but card's characteristics from objectId (split, adventure)
// TODO: search all PLAY_FROM_NOT_OWN_HAND_ZONE and CAST_AS_INSTANT effects and add support of mainCard and objectId // TODO: search all PLAY_FROM_NOT_OWN_HAND_ZONE and CAST_AS_INSTANT effects and add support of mainCard and objectId
PLAY_FROM_NOT_OWN_HAND_ZONE, PLAY_FROM_NOT_OWN_HAND_ZONE(true),
CAST_AS_INSTANT, CAST_AS_INSTANT(true),
ACTIVATE_AS_INSTANT, ACTIVATE_AS_INSTANT,
DAMAGE, DAMAGE,
@ -42,5 +42,19 @@ public enum AsThoughEffectType {
SPEND_OTHER_MANA, SPEND_OTHER_MANA,
SPEND_ONLY_MANA, SPEND_ONLY_MANA,
TARGET TARGET;
private final boolean needPlayCardAbility; // mark effect type as compatible with play/cast abilities
AsThoughEffectType() {
this(false);
}
AsThoughEffectType(boolean needPlayCardAbility) {
this.needPlayCardAbility = needPlayCardAbility;
}
public boolean needPlayCardAbility() {
return needPlayCardAbility;
}
} }