forked from External/mage
* Now token's zcc uses same logic as card's zcc: enters to battlefield with +1 zcc instead +0 zcc * It can improve support of copied spells that store zcc related data (bug example: lost kicked status for copied spell/token); * Kicker abilities - improved support with copied creature spells (example: Verazol, the Split Current, #7431, #7433); * Refactor: simplified kicker code;
313 lines
11 KiB
Java
313 lines
11 KiB
Java
package mage.cards;
|
||
|
||
import mage.MageInt;
|
||
import mage.MageObject;
|
||
import mage.ObjectColor;
|
||
import mage.abilities.*;
|
||
import mage.abilities.costs.mana.ManaCost;
|
||
import mage.abilities.costs.mana.ManaCosts;
|
||
import mage.constants.*;
|
||
import mage.counters.Counters;
|
||
import mage.game.Game;
|
||
import mage.game.GameState;
|
||
import mage.game.events.ZoneChangeEvent;
|
||
import mage.util.CardUtil;
|
||
import mage.util.SubTypes;
|
||
|
||
import java.util.ArrayList;
|
||
import java.util.List;
|
||
import java.util.Set;
|
||
import java.util.UUID;
|
||
|
||
/**
|
||
* @author JayDi85
|
||
*/
|
||
public abstract class ModalDoubleFacesCard extends CardImpl {
|
||
|
||
protected Card leftHalfCard; // main card in all zone
|
||
protected Card rightHalfCard; // second side card, can be only in stack and battlefield zones
|
||
|
||
public ModalDoubleFacesCard(UUID ownerId, CardSetInfo setInfo,
|
||
CardType[] typesLeft, SubType[] subTypesLeft, String costsLeft,
|
||
String secondSideName, CardType[] typesRight, SubType[] subTypesRight, String costsRight) {
|
||
super(ownerId, setInfo, typesLeft, costsLeft + costsRight, SpellAbilityType.MODAL);
|
||
// main card name must be same as left side
|
||
leftHalfCard = new ModalDoubleFacesCardHalfImpl(this.getOwnerId(), new CardSetInfo(setInfo.getName(), setInfo.getExpansionSetCode(), setInfo.getCardNumber(), setInfo.getRarity(), setInfo.getGraphicInfo()),
|
||
typesLeft, subTypesLeft, costsLeft, this, SpellAbilityType.MODAL_LEFT);
|
||
rightHalfCard = new ModalDoubleFacesCardHalfImpl(this.getOwnerId(), new CardSetInfo(secondSideName, setInfo.getExpansionSetCode(), setInfo.getCardNumber(), setInfo.getRarity(), setInfo.getGraphicInfo()),
|
||
typesRight, subTypesRight, costsRight, this, SpellAbilityType.MODAL_RIGHT);
|
||
}
|
||
|
||
public ModalDoubleFacesCard(ModalDoubleFacesCard card) {
|
||
super(card);
|
||
this.leftHalfCard = card.getLeftHalfCard().copy();
|
||
((ModalDoubleFacesCardHalf) leftHalfCard).setParentCard(this);
|
||
this.rightHalfCard = card.rightHalfCard.copy();
|
||
((ModalDoubleFacesCardHalf) rightHalfCard).setParentCard(this);
|
||
}
|
||
|
||
public ModalDoubleFacesCardHalf getLeftHalfCard() {
|
||
return (ModalDoubleFacesCardHalf) leftHalfCard;
|
||
}
|
||
|
||
public ModalDoubleFacesCardHalf getRightHalfCard() {
|
||
return (ModalDoubleFacesCardHalf) rightHalfCard;
|
||
}
|
||
|
||
@Override
|
||
public void assignNewId() {
|
||
super.assignNewId();
|
||
leftHalfCard.assignNewId();
|
||
rightHalfCard.assignNewId();
|
||
}
|
||
|
||
@Override
|
||
public void setCopy(boolean isCopy, MageObject copiedFrom) {
|
||
super.setCopy(isCopy, copiedFrom);
|
||
leftHalfCard.setCopy(isCopy, copiedFrom);
|
||
rightHalfCard.setCopy(isCopy, copiedFrom);
|
||
}
|
||
|
||
@Override
|
||
public boolean moveToZone(Zone toZone, Ability source, Game game, boolean flag, List<UUID> appliedEffects) {
|
||
if (super.moveToZone(toZone, source, game, flag, appliedEffects)) {
|
||
game.getState().setZone(leftHalfCard.getId(), toZone);
|
||
game.getState().setZone(rightHalfCard.getId(), toZone);
|
||
return true;
|
||
}
|
||
return false;
|
||
}
|
||
|
||
@Override
|
||
public void setZone(Zone zone, Game game) {
|
||
super.setZone(zone, game);
|
||
game.setZone(leftHalfCard.getId(), zone);
|
||
game.setZone(rightHalfCard.getId(), zone);
|
||
}
|
||
|
||
@Override
|
||
public boolean moveToExile(UUID exileId, String name, Ability source, Game game, List<UUID> appliedEffects) {
|
||
if (super.moveToExile(exileId, name, source, game, appliedEffects)) {
|
||
Zone currentZone = game.getState().getZone(getId());
|
||
game.getState().setZone(leftHalfCard.getId(), currentZone);
|
||
game.getState().setZone(rightHalfCard.getId(), currentZone);
|
||
return true;
|
||
}
|
||
return false;
|
||
}
|
||
|
||
@Override
|
||
public boolean removeFromZone(Game game, Zone fromZone, Ability source) {
|
||
// zone contains only one main card
|
||
return super.removeFromZone(game, fromZone, source);
|
||
}
|
||
|
||
@Override
|
||
public void updateZoneChangeCounter(Game game, ZoneChangeEvent event) {
|
||
if (isCopy()) { // same as meld cards
|
||
super.updateZoneChangeCounter(game, event);
|
||
return;
|
||
}
|
||
super.updateZoneChangeCounter(game, event);
|
||
leftHalfCard.updateZoneChangeCounter(game, event);
|
||
rightHalfCard.updateZoneChangeCounter(game, event);
|
||
}
|
||
|
||
@Override
|
||
public Counters getCounters(Game game) {
|
||
return getCounters(game.getState());
|
||
}
|
||
|
||
@Override
|
||
public Counters getCounters(GameState state) {
|
||
return state.getCardState(leftHalfCard.getId()).getCounters();
|
||
}
|
||
|
||
@Override
|
||
public boolean cast(Game game, Zone fromZone, SpellAbility ability, UUID controllerId) {
|
||
switch (ability.getSpellAbilityType()) {
|
||
case MODAL_LEFT:
|
||
return this.leftHalfCard.cast(game, fromZone, ability, controllerId);
|
||
case MODAL_RIGHT:
|
||
return this.rightHalfCard.cast(game, fromZone, ability, controllerId);
|
||
default:
|
||
if (this.leftHalfCard.getSpellAbility() != null)
|
||
this.leftHalfCard.getSpellAbility().setControllerId(controllerId);
|
||
if (this.rightHalfCard.getSpellAbility() != null)
|
||
this.rightHalfCard.getSpellAbility().setControllerId(controllerId);
|
||
return super.cast(game, fromZone, ability, controllerId);
|
||
}
|
||
}
|
||
|
||
@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() {
|
||
// 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.getCardType() : cardType;
|
||
}
|
||
|
||
@Override
|
||
public SubTypes getSubtype() {
|
||
// rules: While a double-faced card isn’t on the stack or battlefield, consider only the characteristics of its front face.
|
||
// CardImpl's constructor can call some code on init, so you must check left/right before
|
||
return leftHalfCard != null ? leftHalfCard.getSubtype() : subtype;
|
||
}
|
||
|
||
@Override
|
||
public SubTypes getSubtype(Game game) {
|
||
// rules: While a double-faced card isn’t on the stack or battlefield, consider only the characteristics of its front face.
|
||
// CardImpl's constructor can call some code on init, so you must check left/right before
|
||
return leftHalfCard != null ? leftHalfCard.getSubtype(game) : subtype;
|
||
}
|
||
|
||
@Override
|
||
public boolean hasSubtype(SubType subtype, Game game) {
|
||
return leftHalfCard.hasSubtype(subtype, game);
|
||
}
|
||
|
||
@Override
|
||
public Abilities<Ability> getAbilities() {
|
||
return getInnerAbilities(false);
|
||
}
|
||
|
||
public Abilities<Ability> getSharedAbilities(Game game) {
|
||
// no shared abilities for mdf cards (e.g. must be left or right only)
|
||
return new AbilitiesImpl<>();
|
||
}
|
||
|
||
@Override
|
||
public Abilities<Ability> getAbilities(Game game) {
|
||
return getInnerAbilities(game, false);
|
||
}
|
||
|
||
private boolean isIgnoreDefaultAbility(Ability ability) {
|
||
// ignore default play/spell ability from main card (only halfes are actual)
|
||
// default abilities added on card creation from card type and can't be skipped
|
||
|
||
// skip cast spell
|
||
if (ability instanceof SpellAbility && ((SpellAbility) ability).getSpellAbilityType() == SpellAbilityType.MODAL) {
|
||
return true;
|
||
}
|
||
|
||
// skip play land
|
||
return ability instanceof PlayLandAbility;
|
||
}
|
||
|
||
private Abilities<Ability> getInnerAbilities(Game game, boolean showOnlyMainSide) {
|
||
Abilities<Ability> allAbilites = new AbilitiesImpl<>();
|
||
|
||
for (Ability ability : super.getAbilities(game)) {
|
||
if (isIgnoreDefaultAbility(ability)) {
|
||
continue;
|
||
}
|
||
allAbilites.add(ability);
|
||
}
|
||
|
||
allAbilites.addAll(leftHalfCard.getAbilities(game));
|
||
if (!showOnlyMainSide) {
|
||
allAbilites.addAll(rightHalfCard.getAbilities(game));
|
||
}
|
||
|
||
return allAbilites;
|
||
}
|
||
|
||
private Abilities<Ability> getInnerAbilities(boolean showOnlyMainSide) {
|
||
Abilities<Ability> allAbilites = new AbilitiesImpl<>();
|
||
|
||
for (Ability ability : super.getAbilities()) {
|
||
if (isIgnoreDefaultAbility(ability)) {
|
||
continue;
|
||
}
|
||
allAbilites.add(ability);
|
||
}
|
||
|
||
allAbilites.addAll(leftHalfCard.getAbilities());
|
||
if (!showOnlyMainSide) {
|
||
allAbilites.addAll(rightHalfCard.getAbilities());
|
||
}
|
||
|
||
return allAbilites;
|
||
}
|
||
|
||
@Override
|
||
public List<String> getRules() {
|
||
// rules must show only main side (another side visible by toggle/transform button in GUI)
|
||
// card hints from both sides
|
||
return CardUtil.getCardRulesWithAdditionalInfo(this.getId(), this.getName(),
|
||
this.getInnerAbilities(true), this.getInnerAbilities(false)
|
||
);
|
||
}
|
||
|
||
@Override
|
||
public List<String> getRules(Game game) {
|
||
// rules must show only main side (another side visible by toggle/transform button in GUI)
|
||
// card hints from both sides
|
||
return CardUtil.getCardRulesWithAdditionalInfo(game, this.getId(), this.getName(),
|
||
this.getInnerAbilities(game, true), this.getInnerAbilities(game, false)
|
||
);
|
||
}
|
||
|
||
@Override
|
||
public boolean hasAbility(Ability ability, Game game) {
|
||
return super.hasAbility(ability, game);
|
||
}
|
||
|
||
@Override
|
||
public ObjectColor getColor() {
|
||
return leftHalfCard.getColor();
|
||
}
|
||
|
||
@Override
|
||
public ObjectColor getColor(Game game) {
|
||
return leftHalfCard.getColor(game);
|
||
}
|
||
|
||
@Override
|
||
public ObjectColor getFrameColor(Game game) {
|
||
return leftHalfCard.getFrameColor(game);
|
||
}
|
||
|
||
@Override
|
||
public void setOwnerId(UUID ownerId) {
|
||
super.setOwnerId(ownerId);
|
||
abilities.setControllerId(ownerId);
|
||
leftHalfCard.getAbilities().setControllerId(ownerId);
|
||
leftHalfCard.setOwnerId(ownerId);
|
||
rightHalfCard.getAbilities().setControllerId(ownerId);
|
||
rightHalfCard.setOwnerId(ownerId);
|
||
}
|
||
|
||
@Override
|
||
public ManaCosts<ManaCost> getManaCost() {
|
||
return leftHalfCard.getManaCost();
|
||
}
|
||
|
||
@Override
|
||
public int getConvertedManaCost() {
|
||
// Rules:
|
||
// The converted mana cost of a modal double-faced card is based on the characteristics of the
|
||
// face that’s being considered. On the stack and battlefield, consider whichever face is up.
|
||
// In all other zones, consider only the front face. This is different than how the converted
|
||
// mana cost of a transforming double-faced card is determined.
|
||
|
||
// on stack or battlefield it must be half card with own cost
|
||
return leftHalfCard.getConvertedManaCost();
|
||
}
|
||
|
||
@Override
|
||
public MageInt getPower() {
|
||
return leftHalfCard.getPower();
|
||
}
|
||
|
||
@Override
|
||
public MageInt getToughness() {
|
||
return leftHalfCard.getToughness();
|
||
}
|
||
}
|