New commander types support:

* [KHM] fixed legendary status of mdf cards (closes #7370, #7404, #7465, #7481);
* Game: added support of split cards as commander (signature spell);
* Game: added support of adventure cards as commander;
* Game: added support of modal double faces cards as commander;
This commit is contained in:
Oleg Agafonov 2021-02-02 06:40:49 +04:00
parent bc72384f0d
commit 50e5809a79
9 changed files with 314 additions and 35 deletions

View file

@ -41,7 +41,12 @@ public class CommanderCostModification extends CostModificationEffectImpl {
@Override
public boolean applies(Ability abilityToModify, Ability source, Game game) {
return commander.getId().equals(abilityToModify.getSourceId())
Card cardToCheck = game.getCard(abilityToModify.getSourceId()); // split/mdf cards support
if (cardToCheck == null) {
return false;
}
return commander.getId().equals(cardToCheck.getMainCard().getId())
&& (abilityToModify instanceof CastCommanderAbility
|| abilityToModify instanceof PlayLandAsCommanderAbility);
}

View file

@ -86,13 +86,13 @@ public interface Card extends MageObject {
* @param zone
* @param source ability which calls that move, can be null
* @param game
* @param flag If zone
* <ul>
* <li>LIBRARY: <ul><li>true - put on top</li><li>false - put on
* bottom</li></ul></li>
* <li>BATTLEFIELD: <ul><li>true - tapped</li><li>false -
* untapped</li></ul></li>
* </ul>
* @param flag If zone
* <ul>
* <li>LIBRARY: <ul><li>true - put on top</li><li>false - put on
* bottom</li></ul></li>
* <li>BATTLEFIELD: <ul><li>true - tapped</li><li>false -
* untapped</li></ul></li>
* </ul>
* @return true if card was moved to zone
*/
boolean moveToZone(Zone zone, Ability source, Game game, boolean flag);
@ -102,8 +102,8 @@ public interface Card extends MageObject {
/**
* Moves the card to an exile zone
*
* @param exileId set to null for generic exile zone
* @param name used for exile zone with the specified exileId
* @param exileId set to null for generic exile zone
* @param name used for exile zone with the specified exileId
* @param source
* @param game
* @return true if card was moved to zone
@ -183,7 +183,7 @@ public interface Card extends MageObject {
default boolean commanderCost(Game game, Ability source, Ability abilityToModify) {
CommanderPlaysCountWatcher watcher = game.getState().getWatcher(CommanderPlaysCountWatcher.class);
int castCount = watcher.getPlaysCount(getId());
int castCount = watcher.getPlaysCount(getMainCard().getId());
if (castCount > 0) {
abilityToModify.getManaCostsToPay().add(ManaUtil.createManaCost(2 * castCount, false));
}

View file

@ -13,8 +13,8 @@ import mage.util.CardUtil;
import mage.util.SubTypes;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List;
import java.util.Set;
import java.util.UUID;
/**
@ -127,6 +127,12 @@ public abstract class ModalDoubleFacesCard extends CardImpl {
}
}
@Override
public Set<SuperType> getSuperType() {
// CardImpl's constructor can call some code on init, so you must check left/right before
// it's a bad workaround
return leftHalfCard != null ? leftHalfCard.getSuperType() : supertype;
}
@Override
public ArrayList<CardType> getCardType() {
@ -154,11 +160,6 @@ public abstract class ModalDoubleFacesCard extends CardImpl {
return leftHalfCard.hasSubtype(subtype, game);
}
@Override
public EnumSet<SuperType> getSuperType() {
return EnumSet.noneOf(SuperType.class);
}
@Override
public Abilities<Ability> getAbilities() {
return getInnerAbilities(false);

View file

@ -12,7 +12,6 @@ import mage.abilities.text.TextPart;
import mage.cards.Card;
import mage.cards.FrameStyle;
import mage.constants.CardType;
import mage.constants.SpellAbilityType;
import mage.constants.SubType;
import mage.constants.SuperType;
import mage.game.Game;
@ -36,16 +35,30 @@ public class Commander implements CommandObject {
this.sourceObject = card;
// replace spell ability by commander cast spell (to cast from command zone)
if (card.getSpellAbility() != null) {
abilities.add(new CastCommanderAbility(card, card.getSpellAbility()));
}
// replace alternative spell abilities by commander cast spell (to cast from command zone)
for (Ability ability : card.getAbilities()) {
if (ability instanceof SpellAbility) {
SpellAbility spellAbility = (SpellAbility) ability;
if (spellAbility.getSpellAbilityType() == SpellAbilityType.BASE_ALTERNATE) {
abilities.add(new CastCommanderAbility(card, spellAbility));
switch (spellAbility.getSpellAbilityType()) {
case BASE:
case BASE_ALTERNATE:
case SPLIT:
case SPLIT_FUSED:
case SPLIT_LEFT:
case SPLIT_RIGHT:
case MODAL:
case MODAL_LEFT:
case MODAL_RIGHT:
case ADVENTURE_SPELL:
// can be used from command zone
abilities.add(new CastCommanderAbility(card, spellAbility));
break;
case FACE_DOWN_CREATURE: // dynamic added spell for alternative cost like cast as face down
case SPLICE: // only from hand
case SPLIT_AFTERMATH: // only from graveyard
// can't use from command zone
break;
default:
throw new IllegalArgumentException("Error, unknown spell type in commander card: " + spellAbility.getSpellAbilityType() + " from " + card.getName());
}
}
}

View file

@ -5,6 +5,7 @@ import mage.constants.Zone;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.events.GameEvent.EventType;
import mage.util.CardUtil;
import mage.watchers.Watcher;
import java.util.*;
@ -12,7 +13,7 @@ import java.util.*;
/**
* Calcs commanders play count only from command zone (spell or land)
* Cards like Remand can put command to hand and cast it without commander tax increase
*
* <p>
* Warning, if your code can be called in non commander games then you must watcher in your ability
* (example: you are using watcher in trigger, hint or effect, but do not checking another things like commander source or cost)
*
@ -33,14 +34,17 @@ public class CommanderPlaysCountWatcher extends Watcher {
&& event.getType() != EventType.SPELL_CAST) {
return;
}
// must control main cards (split/mdf cards support)
final UUID objectId;
if (event.getType() == EventType.LAND_PLAYED) {
objectId = event.getTargetId();
objectId = CardUtil.getMainCardId(game, event.getTargetId());
} else if (event.getType() == EventType.SPELL_CAST) {
objectId = event.getSourceId();
objectId = CardUtil.getMainCardId(game, event.getSourceId());
} else {
objectId = null;
}
boolean isCommanderObject = game
.getPlayerList()
.stream()
@ -51,8 +55,8 @@ public class CommanderPlaysCountWatcher extends Watcher {
if (!isCommanderObject || event.getZone() != Zone.COMMAND) {
return;
}
playsCount.putIfAbsent(event.getSourceId(), 0);
playsCount.computeIfPresent(event.getSourceId(), (u, i) -> i + 1);
playsCount.putIfAbsent(objectId, 0);
playsCount.computeIfPresent(objectId, (u, i) -> i + 1);
playerCount.putIfAbsent(event.getPlayerId(), 0);
playerCount.compute(event.getPlayerId(), (u, i) -> i + 1);
}