mirror of
https://github.com/magefree/mage.git
synced 2025-12-23 03:51:58 -08:00
* Bolas's Citadel - fixed that it can't play cards with cycling (#6215);
This commit is contained in:
parent
cf759e0443
commit
c835fb409d
4 changed files with 115 additions and 33 deletions
|
|
@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
*/
|
*/
|
||||||
|
|
@ -334,7 +338,7 @@ public class ContinuousEffects implements Serializable {
|
||||||
}
|
}
|
||||||
// boolean checkLKI = event.getType().equals(EventType.ZONE_CHANGE) || event.getType().equals(EventType.DESTROYED_PERMANENT);
|
// boolean checkLKI = event.getType().equals(EventType.ZONE_CHANGE) || event.getType().equals(EventType.DESTROYED_PERMANENT);
|
||||||
//get all applicable transient Replacement effects
|
//get all applicable transient Replacement effects
|
||||||
for (Iterator<ReplacementEffect> iterator = replacementEffects.iterator(); iterator.hasNext();) {
|
for (Iterator<ReplacementEffect> iterator = replacementEffects.iterator(); iterator.hasNext(); ) {
|
||||||
ReplacementEffect effect = iterator.next();
|
ReplacementEffect effect = iterator.next();
|
||||||
if (!effect.checksEventType(event, game)) {
|
if (!effect.checksEventType(event, game)) {
|
||||||
continue;
|
continue;
|
||||||
|
|
@ -367,7 +371,7 @@ public class ContinuousEffects implements Serializable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (Iterator<PreventionEffect> iterator = preventionEffects.iterator(); iterator.hasNext();) {
|
for (Iterator<PreventionEffect> iterator = preventionEffects.iterator(); iterator.hasNext(); ) {
|
||||||
PreventionEffect effect = iterator.next();
|
PreventionEffect effect = iterator.next();
|
||||||
if (!effect.checksEventType(event, game)) {
|
if (!effect.checksEventType(event, game)) {
|
||||||
continue;
|
continue;
|
||||||
|
|
@ -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;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -772,7 +785,7 @@ public class ContinuousEffects implements Serializable {
|
||||||
do {
|
do {
|
||||||
Map<ReplacementEffect, Set<Ability>> rEffects = getApplicableReplacementEffects(event, game);
|
Map<ReplacementEffect, Set<Ability>> rEffects = getApplicableReplacementEffects(event, game);
|
||||||
// Remove all consumed effects (ability dependant)
|
// Remove all consumed effects (ability dependant)
|
||||||
for (Iterator<ReplacementEffect> it1 = rEffects.keySet().iterator(); it1.hasNext();) {
|
for (Iterator<ReplacementEffect> it1 = rEffects.keySet().iterator(); it1.hasNext(); ) {
|
||||||
ReplacementEffect entry = it1.next();
|
ReplacementEffect entry = it1.next();
|
||||||
if (consumed.containsKey(entry.getId()) /*&& !(entry instanceof CommanderReplacementEffect) */) { // 903.9.
|
if (consumed.containsKey(entry.getId()) /*&& !(entry instanceof CommanderReplacementEffect) */) { // 903.9.
|
||||||
Set<UUID> consumedAbilitiesIds = consumed.get(entry.getId());
|
Set<UUID> consumedAbilitiesIds = consumed.get(entry.getId());
|
||||||
|
|
@ -963,7 +976,7 @@ public class ContinuousEffects implements Serializable {
|
||||||
|
|
||||||
if (!waitingEffects.isEmpty()) {
|
if (!waitingEffects.isEmpty()) {
|
||||||
// check if waiting effects can be applied now
|
// check if waiting effects can be applied now
|
||||||
for (Iterator<Map.Entry<ContinuousEffect, Set<UUID>>> iterator = waitingEffects.entrySet().iterator(); iterator.hasNext();) {
|
for (Iterator<Map.Entry<ContinuousEffect, Set<UUID>>> iterator = waitingEffects.entrySet().iterator(); iterator.hasNext(); ) {
|
||||||
Map.Entry<ContinuousEffect, Set<UUID>> entry = iterator.next();
|
Map.Entry<ContinuousEffect, Set<UUID>> entry = iterator.next();
|
||||||
if (appliedEffects.containsAll(entry.getValue())) { // all dependent to effects are applied now so apply the effect itself
|
if (appliedEffects.containsAll(entry.getValue())) { // all dependent to effects are applied now so apply the effect itself
|
||||||
appliedAbilities = appliedEffectAbilities.get(entry.getKey());
|
appliedAbilities = appliedEffectAbilities.get(entry.getKey());
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue