Added modal double faces cards implementation (MDF cards, #7012)

This commit is contained in:
Oleg Agafonov 2020-09-30 04:02:33 +04:00
parent bbed5a16b8
commit 8ac78b4b9e
60 changed files with 1128 additions and 764 deletions

View file

@ -3,13 +3,13 @@ package mage.abilities.effects;
import mage.abilities.Ability;
import mage.abilities.ActivatedAbility;
import mage.cards.Card;
import mage.cards.ModalDoubleFacesCard;
import mage.cards.SplitCard;
import mage.constants.*;
import mage.game.Game;
import mage.players.Player;
import java.util.UUID;
import mage.cards.SplitCard;
import mage.cards.SplitCardHalf;
import mage.players.Player;
/**
* @author BetaSteward_at_googlemail.com
@ -22,7 +22,7 @@ public abstract class AsThoughEffectImpl extends ContinuousEffectImpl implements
public AsThoughEffectImpl(AsThoughEffectType type, Duration duration, Outcome outcome) {
this(type, duration, outcome, false);
}
public AsThoughEffectImpl(AsThoughEffectType type, Duration duration, Outcome outcome, boolean consumable) {
super(duration, outcome);
this.type = type;
@ -74,12 +74,12 @@ public abstract class AsThoughEffectImpl extends ContinuousEffectImpl implements
* Internal method to do the neccessary to allow the card from objectId to be cast or played (if it's a land) without paying any mana.
* Additional costs (like sacrificing or discarding) have still to be payed.
* Checks if the card is of the correct type or in the correct zone have to be done before.
*
* @param objectId sourceId of the card to play
* @param source source ability that allows this effect
*
* @param objectId sourceId of the card to play
* @param source source ability that allows this effect
* @param affectedControllerId player allowed to play the card
* @param game
* @return
* @return
*/
protected boolean allowCardToPlayWithoutMana(UUID objectId, Ability source, UUID affectedControllerId, Game game) {
Player player = game.getPlayer(affectedControllerId);
@ -89,9 +89,14 @@ public abstract class AsThoughEffectImpl extends ContinuousEffectImpl implements
}
if (!card.isLand()) {
if (card instanceof SplitCard) {
SplitCardHalf leftCard = ((SplitCard) card).getLeftHalfCard();
Card leftCard = ((SplitCard) card).getLeftHalfCard();
player.setCastSourceIdWithAlternateMana(leftCard.getId(), null, leftCard.getSpellAbility().getCosts());
SplitCardHalf rightCard = ((SplitCard) card).getRightHalfCard();
Card rightCard = ((SplitCard) card).getRightHalfCard();
player.setCastSourceIdWithAlternateMana(rightCard.getId(), null, rightCard.getSpellAbility().getCosts());
} else if (card instanceof ModalDoubleFacesCard) {
Card leftCard = ((ModalDoubleFacesCard) card).getLeftHalfCard();
player.setCastSourceIdWithAlternateMana(leftCard.getId(), null, leftCard.getSpellAbility().getCosts());
Card rightCard = ((ModalDoubleFacesCard) card).getRightHalfCard();
player.setCastSourceIdWithAlternateMana(rightCard.getId(), null, rightCard.getSpellAbility().getCosts());
} else {
player.setCastSourceIdWithAlternateMana(objectId, null, card.getSpellAbility().getCosts());

View file

@ -1,5 +1,6 @@
package mage.abilities.effects;
import mage.ApprovingObject;
import mage.MageObject;
import mage.abilities.Ability;
import mage.abilities.MageSingleton;
@ -7,6 +8,8 @@ import mage.abilities.StaticAbility;
import mage.abilities.effects.common.continuous.BecomesFaceDownCreatureEffect;
import mage.abilities.effects.common.continuous.CommanderReplacementEffect;
import mage.cards.*;
import mage.choices.Choice;
import mage.choices.ChoiceImpl;
import mage.constants.*;
import mage.filter.FilterCard;
import mage.filter.predicate.Predicate;
@ -29,9 +32,6 @@ import java.io.Serializable;
import java.util.*;
import java.util.Map.Entry;
import java.util.stream.Collectors;
import mage.ApprovingObject;
import mage.choices.Choice;
import mage.choices.ChoiceImpl;
/**
* @author BetaSteward_at_googlemail.com
@ -334,7 +334,7 @@ public class ContinuousEffects implements Serializable {
* @param event
* @param game
* @return a list of all {@link ReplacementEffect} that apply to the current
* event
* event
*/
private Map<ReplacementEffect, Set<Ability>> getApplicableReplacementEffects(GameEvent event, Game game) {
Map<ReplacementEffect, Set<Ability>> replaceEffects = new HashMap<>();
@ -343,7 +343,7 @@ public class ContinuousEffects implements Serializable {
}
// boolean checkLKI = event.getType().equals(EventType.ZONE_CHANGE) || event.getType().equals(EventType.DESTROYED_PERMANENT);
//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();
if (!effect.checksEventType(event, game)) {
continue;
@ -376,7 +376,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();
if (!effect.checksEventType(event, game)) {
continue;
@ -507,7 +507,7 @@ public class ContinuousEffects implements Serializable {
* @param controllerId
* @param game
* @return sourceId of the permitting effect if any exists otherwise returns
* null
* null
*/
public ApprovingObject asThough(UUID objectId, AsThoughEffectType type, Ability affectedAbility, UUID controllerId, Game game) {
List<AsThoughEffect> asThoughEffectsList = getApplicableAsThoughEffects(type, game);
@ -515,6 +515,8 @@ public class ContinuousEffects implements Serializable {
UUID idToCheck;
if (affectedAbility != null && affectedAbility.getSourceObject(game) instanceof SplitCardHalf) {
idToCheck = ((SplitCardHalf) affectedAbility.getSourceObject(game)).getParentCard().getId();
} else if (affectedAbility != null && affectedAbility.getSourceObject(game) instanceof ModalDoubleFacesCardHalf) {
idToCheck = ((ModalDoubleFacesCardHalf) affectedAbility.getSourceObject(game)).getParentCard().getId();
} else if (affectedAbility != null && affectedAbility.getSourceObject(game) instanceof AdventureCardSpell
&& !type.needPlayCardAbility()) {
// adventure spell uses alternative characteristics for spell/stack
@ -523,6 +525,8 @@ public class ContinuousEffects implements Serializable {
Card card = game.getCard(objectId);
if (card instanceof SplitCardHalf) {
idToCheck = ((SplitCardHalf) card).getParentCard().getId();
} else if (card instanceof ModalDoubleFacesCardHalf) {
idToCheck = ((ModalDoubleFacesCardHalf) card).getParentCard().getId();
} else if (card instanceof AdventureCardSpell
&& !type.needPlayCardAbility()) {
// adventure spell uses alternative characteristics for spell/stack
@ -568,24 +572,24 @@ public class ContinuousEffects implements Serializable {
} else if (possibleApprovingObjects.size() > 1) {
// Select the ability that you use to permit the action
Map<String, String> keyChoices = new HashMap<>();
for(ApprovingObject approvingObject :possibleApprovingObjects) {
MageObject mageObject = game.getObject(approvingObject.getApprovingAbility().getSourceId());
keyChoices.put(approvingObject.getApprovingAbility().getId().toString(),
for (ApprovingObject approvingObject : possibleApprovingObjects) {
MageObject mageObject = game.getObject(approvingObject.getApprovingAbility().getSourceId());
keyChoices.put(approvingObject.getApprovingAbility().getId().toString(),
(approvingObject.getApprovingAbility().getRule(mageObject == null ? "" : mageObject.getName()))
+ (mageObject == null ? "" : " (" + mageObject.getIdName() + ")"));
+ (mageObject == null ? "" : " (" + mageObject.getIdName() + ")"));
}
Choice choicePermitting = new ChoiceImpl(true);
choicePermitting.setMessage("Choose the permitting object");
choicePermitting.setKeyChoices(keyChoices);
Player player = game.getPlayer(controllerId);
player.choose(Outcome.Detriment, choicePermitting, game);
for(ApprovingObject approvingObject: possibleApprovingObjects) {
for (ApprovingObject approvingObject : possibleApprovingObjects) {
if (approvingObject.getApprovingAbility().getId().toString().equals(choicePermitting.getChoiceKey())) {
return approvingObject;
}
}
}
}
return null;
@ -833,7 +837,7 @@ public class ContinuousEffects implements Serializable {
do {
Map<ReplacementEffect, Set<Ability>> rEffects = getApplicableReplacementEffects(event, game);
// 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();
if (consumed.containsKey(entry.getId()) /*&& !(entry instanceof CommanderReplacementEffect) */) { // 903.9.
Set<UUID> consumedAbilitiesIds = consumed.get(entry.getId());
@ -1018,7 +1022,7 @@ public class ContinuousEffects implements Serializable {
.entrySet()
.stream()
.filter(entry -> dependentTo.contains(entry.getKey().getId())
&& entry.getValue().contains(effect.getId()))
&& entry.getValue().contains(effect.getId()))
.forEach(entry -> {
entry.getValue().remove(effect.getId());
dependentTo.remove(entry.getKey().getId());
@ -1052,7 +1056,7 @@ public class ContinuousEffects implements Serializable {
continue;
}
// 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();
if (!appliedEffects.containsAll(entry.getValue())) { // all dependent to effects are applied now so apply the effect itself
continue;

View file

@ -8,6 +8,7 @@ import mage.abilities.condition.common.SourceIsSpellCondition;
import mage.abilities.costs.AlternativeCostSourceAbility;
import mage.abilities.effects.ContinuousEffectImpl;
import mage.cards.Card;
import mage.cards.ModalDoubleFacesCardHalf;
import mage.cards.SplitCardHalf;
import mage.constants.*;
import mage.filter.FilterCard;
@ -21,7 +22,7 @@ import java.util.UUID;
public class CastFromHandWithoutPayingManaCostEffect extends ContinuousEffectImpl {
private final AlternativeCostSourceAbility alternativeCastingCostAbility;
public CastFromHandWithoutPayingManaCostEffect() {
this(StaticFilters.FILTER_CARDS_NON_LAND, true);
}
@ -37,7 +38,7 @@ public class CastFromHandWithoutPayingManaCostEffect extends ContinuousEffectImp
condition = new CompoundCondition(SourceIsSpellCondition.instance, IsBeingCastFromHandCondition.instance);
} else {
condition = SourceIsSpellCondition.instance;
}
}
this.alternativeCastingCostAbility = new AlternativeCostSourceAbility(null, condition, null, filter, true);
this.staticText = "You may cast " + filter.getMessage()
+ (fromHand ? " from your hand" : "")
@ -87,9 +88,9 @@ enum IsBeingCastFromHandCondition implements Condition {
@Override
public boolean apply(Game game, Ability source) {
MageObject object = game.getObject(source.getSourceId());
if (object instanceof SplitCardHalf) {
UUID splitCardId = ((Card) object).getMainCard().getId();
object = game.getObject(splitCardId);
if (object instanceof SplitCardHalf || object instanceof ModalDoubleFacesCardHalf) {
UUID mainCardId = ((Card) object).getMainCard().getId();
object = game.getObject(mainCardId);
}
if (object instanceof Spell) { // needed to check if it can be cast by alternate cost
Spell spell = (Spell) object;

View file

@ -1,24 +1,31 @@
package mage.abilities.keyword;
import java.util.UUID;
import mage.abilities.Ability;
import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.effects.*;
import mage.abilities.effects.AsThoughEffectImpl;
import mage.abilities.effects.ContinuousRuleModifyingEffectImpl;
import mage.abilities.effects.ReplacementEffectImpl;
import mage.cards.Card;
import mage.cards.ModalDoubleFacesCardHalf;
import mage.cards.SplitCardHalf;
import mage.constants.*;
import mage.constants.AsThoughEffectType;
import mage.constants.Duration;
import mage.constants.Outcome;
import mage.constants.Zone;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.events.ZoneChangeEvent;
import mage.game.stack.Spell;
import mage.players.Player;
import java.util.UUID;
/**
* Aftermath
*
* <p>
* TODO: Implement once we get details on the comprehensive rules meaning of the
* ability
*
* <p>
* Current text is a shell copied from Flashback
*
* @author stravant
@ -113,9 +120,7 @@ class AftermathCantCastFromHand extends ContinuousRuleModifyingEffectImpl {
Card card = game.getCard(event.getSourceId());
if (card != null && card.getId().equals(source.getSourceId())) {
Zone zone = game.getState().getZone(card.getId());
if (zone != null && (zone != Zone.GRAVEYARD)) {
return true;
}
return zone != null && (zone != Zone.GRAVEYARD);
}
return false;
}
@ -155,14 +160,16 @@ class AftermathExileAsResolvesFromGraveyard extends ReplacementEffectImpl {
sourceCard = ((SplitCardHalf) sourceCard).getParentCard();
sourceId = sourceCard.getId();
}
if (sourceCard instanceof ModalDoubleFacesCardHalf) {
sourceCard = ((ModalDoubleFacesCardHalf) sourceCard).getParentCard();
sourceId = sourceCard.getId();
}
if (event.getTargetId().equals(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;
}
// And this spell was cast from the graveyard, so we need to exile it
return spell != null && spell.getFromZone() == Zone.GRAVEYARD;
}
}
return false;
@ -174,6 +181,9 @@ class AftermathExileAsResolvesFromGraveyard extends ReplacementEffectImpl {
if (sourceCard instanceof SplitCardHalf) {
sourceCard = ((SplitCardHalf) sourceCard).getParentCard();
}
if (sourceCard instanceof ModalDoubleFacesCardHalf) {
sourceCard = ((ModalDoubleFacesCardHalf) sourceCard).getParentCard();
}
if (sourceCard != null) {
Player player = game.getPlayer(sourceCard.getOwnerId());
if (player != null) {

View file

@ -7,6 +7,7 @@ import mage.abilities.costs.Costs;
import mage.abilities.effects.ContinuousEffect;
import mage.abilities.effects.ReplacementEffectImpl;
import mage.cards.Card;
import mage.cards.ModalDoubleFacesCard;
import mage.cards.SplitCard;
import mage.constants.*;
import mage.game.Game;
@ -68,12 +69,18 @@ public class FlashbackAbility extends SpellAbility {
}
// Flashback can never cast a split card by Fuse, because Fuse only works from hand
// https://tappedout.net/mtg-questions/snapcaster-mage-and-flashback-on-a-fuse-card-one-or-both-halves-legal-targets/
if (card.isSplitCard()) {
if (card instanceof SplitCard) {
if (((SplitCard) card).getLeftHalfCard().getName().equals(abilityName)) {
return ((SplitCard) card).getLeftHalfCard().getSpellAbility().canActivate(playerId, game);
} else if (((SplitCard) card).getRightHalfCard().getName().equals(abilityName)) {
return ((SplitCard) card).getRightHalfCard().getSpellAbility().canActivate(playerId, game);
}
} else if (card instanceof ModalDoubleFacesCard) {
if (((ModalDoubleFacesCard) card).getLeftHalfCard().getName().equals(abilityName)) {
return ((ModalDoubleFacesCard) card).getLeftHalfCard().getSpellAbility().canActivate(playerId, game);
} else if (((ModalDoubleFacesCard) card).getRightHalfCard().getName().equals(abilityName)) {
return ((ModalDoubleFacesCard) card).getRightHalfCard().getSpellAbility().canActivate(playerId, game);
}
}
return card.getSpellAbility().canActivate(playerId, game);
}
@ -87,12 +94,18 @@ public class FlashbackAbility extends SpellAbility {
if (card != null) {
if (spellAbilityToResolve == null) {
SpellAbility spellAbilityCopy = null;
if (card.isSplitCard()) {
if (card instanceof SplitCard) {
if (((SplitCard) card).getLeftHalfCard().getName().equals(abilityName)) {
spellAbilityCopy = ((SplitCard) card).getLeftHalfCard().getSpellAbility().copy();
} else if (((SplitCard) card).getRightHalfCard().getName().equals(abilityName)) {
spellAbilityCopy = ((SplitCard) card).getRightHalfCard().getSpellAbility().copy();
}
} else if (card instanceof ModalDoubleFacesCard) {
if (((ModalDoubleFacesCard) card).getLeftHalfCard().getName().equals(abilityName)) {
spellAbilityCopy = ((ModalDoubleFacesCard) card).getLeftHalfCard().getSpellAbility().copy();
} else if (((ModalDoubleFacesCard) card).getRightHalfCard().getName().equals(abilityName)) {
spellAbilityCopy = ((ModalDoubleFacesCard) card).getRightHalfCard().getSpellAbility().copy();
}
} else {
spellAbilityCopy = card.getSpellAbility().copy();
}

View file

@ -1,8 +1,3 @@
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package mage.cards;
import mage.abilities.Modes;

View file

@ -1,7 +1,5 @@
package mage.cards;
import java.util.List;
import java.util.UUID;
import mage.MageObject;
import mage.Mana;
import mage.abilities.Abilities;
@ -16,6 +14,9 @@ import mage.game.Game;
import mage.game.GameState;
import mage.game.permanent.Permanent;
import java.util.List;
import java.util.UUID;
public interface Card extends MageObject {
UUID getOwnerId();
@ -29,8 +30,9 @@ public interface Card extends MageObject {
/**
* For cards: return all basic and dynamic abilities
* For permanents: return all basic and dynamic abilities
*
* @param game
* @return
* @return
*/
Abilities<Ability> getAbilities(Game game);
@ -62,8 +64,6 @@ public interface Card extends MageObject {
String getFlipCardName();
boolean isSplitCard();
boolean isTransformable();
void setTransformable(boolean transformable);
@ -76,21 +76,23 @@ public interface Card extends MageObject {
void addInfo(String key, String value, Game game);
// WARNING, don't add new move/remove methods (if you add then you must override it in all multi-part cards like Split or MDF)
/**
* Moves the card to the specified zone
*
* @param zone
* @param sourceId
* @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>
* <li>GRAVEYARD: <ul><li>true - not from Battlefield</li><li>false - from
* Battlefield</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>
* <li>GRAVEYARD: <ul><li>true - not from Battlefield</li><li>false - from
* Battlefield</li></ul></li>
* </ul>
* @return true if card was moved to zone
*/
boolean moveToZone(Zone zone, UUID sourceId, Game game, boolean flag);
@ -100,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 sourceId
* @param game
* @return true if card was moved to zone
@ -112,6 +114,7 @@ public interface Card extends MageObject {
boolean cast(Game game, Zone fromZone, SpellAbility ability, UUID controllerId);
// WARNING, don't add new move/remove methods (if you add then you must override it in all multi-parts card like Split Half or MDF Half)
boolean removeFromZone(Game game, Zone fromZone, UUID sourceId);
boolean putOntoBattlefield(Game game, Zone fromZone, UUID sourceId, UUID controllerId);

View file

@ -57,7 +57,6 @@ public abstract class CardImpl extends MageObjectImpl implements Card {
protected boolean flipCard;
protected String flipCardName;
protected boolean usesVariousArt = false;
protected boolean splitCard;
protected boolean morphCard;
protected boolean modalDFC; // modal double faces card
@ -139,7 +138,6 @@ public abstract class CardImpl extends MageObjectImpl implements Card {
flipCard = card.flipCard;
flipCardName = card.flipCardName;
usesVariousArt = card.usesVariousArt;
splitCard = card.splitCard;
morphCard = card.morphCard;
modalDFC = card.modalDFC;
@ -563,13 +561,22 @@ public abstract class CardImpl extends MageObjectImpl implements Card {
stackObject = game.getStack().getSpell(this.getId(), false);
}
if (stackObject == null && (this instanceof SplitCard)) { // handle if half of Split cast is on the stack
// handle half of Split Cards on stack
if (stackObject == null && (this instanceof SplitCard)) {
stackObject = game.getStack().getSpell(((SplitCard) this).getLeftHalfCard().getId(), false);
if (stackObject == null) {
stackObject = game.getStack().getSpell(((SplitCard) this).getRightHalfCard().getId(), false);
}
}
// handle half of Modal Double Faces Cards on stack
if (stackObject == null && (this instanceof ModalDoubleFacesCard)) {
stackObject = game.getStack().getSpell(((ModalDoubleFacesCard) this).getLeftHalfCard().getId(), false);
if (stackObject == null) {
stackObject = game.getStack().getSpell(((ModalDoubleFacesCard) this).getRightHalfCard().getId(), false);
}
}
if (stackObject == null && (this instanceof AdventureCard)) {
stackObject = game.getStack().getSpell(((AdventureCard) this).getSpellCard().getId(), false);
}
@ -687,10 +694,6 @@ public abstract class CardImpl extends MageObjectImpl implements Card {
@Override
public final Card getSecondCardFace() {
// TODO: remove when MDFCs are implemented
if (modalDFC) {
return null;
}
// init second side card on first call
if (secondSideCardClazz == null && secondSideCard == null) {
return null;
@ -726,11 +729,6 @@ public abstract class CardImpl extends MageObjectImpl implements Card {
return flipCardName;
}
@Override
public boolean isSplitCard() {
return splitCard;
}
@Override
public boolean getUsesVariousArt() {
return usesVariousArt;

View file

@ -0,0 +1,184 @@
package mage.cards;
import mage.MageObject;
import mage.abilities.Abilities;
import mage.abilities.AbilitiesImpl;
import mage.abilities.Ability;
import mage.abilities.SpellAbility;
import mage.constants.CardType;
import mage.constants.SpellAbilityType;
import mage.constants.SubType;
import mage.constants.Zone;
import mage.game.Game;
import mage.game.events.ZoneChangeEvent;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
/**
* @author JayDi85
*/
public abstract class ModalDoubleFacesCard extends CardImpl {
protected Card leftHalfCard;
protected Card rightHalfCard;
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);
this.modalDFC = true;
}
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, UUID sourceId, Game game, boolean flag, List<UUID> appliedEffects) {
if (super.moveToZone(toZone, sourceId, game, flag, appliedEffects)) {
game.getState().setZone(getLeftHalfCard().getId(), toZone);
game.getState().setZone(getRightHalfCard().getId(), toZone);
return true;
}
return false;
}
@Override
public void setZone(Zone zone, Game game) {
super.setZone(zone, game);
game.setZone(getLeftHalfCard().getId(), zone);
game.setZone(getRightHalfCard().getId(), zone);
}
@Override
public boolean moveToExile(UUID exileId, String name, UUID sourceId, Game game, List<UUID> appliedEffects) {
if (super.moveToExile(exileId, name, sourceId, game, appliedEffects)) {
Zone currentZone = game.getState().getZone(getId());
game.getState().setZone(getLeftHalfCard().getId(), currentZone);
game.getState().setZone(getRightHalfCard().getId(), currentZone);
return true;
}
return false;
}
@Override
public boolean removeFromZone(Game game, Zone fromZone, UUID sourceId) {
// zone contains only one main card
return super.removeFromZone(game, fromZone, sourceId);
}
@Override
public void updateZoneChangeCounter(Game game, ZoneChangeEvent event) {
if (isCopy()) { // same as meld cards
super.updateZoneChangeCounter(game, event);
return;
}
super.updateZoneChangeCounter(game, event);
getLeftHalfCard().updateZoneChangeCounter(game, event);
getRightHalfCard().updateZoneChangeCounter(game, event);
}
@Override
public boolean cast(Game game, Zone fromZone, SpellAbility ability, UUID controllerId) {
switch (ability.getSpellAbilityType()) {
case MODAL_LEFT:
return this.getLeftHalfCard().cast(game, fromZone, ability, controllerId);
case MODAL_RIGHT:
return this.getRightHalfCard().cast(game, fromZone, ability, controllerId);
default:
this.getLeftHalfCard().getSpellAbility().setControllerId(controllerId);
this.getRightHalfCard().getSpellAbility().setControllerId(controllerId);
return super.cast(game, fromZone, ability, controllerId);
}
}
@Override
public Abilities<Ability> getAbilities() {
Abilities<Ability> allAbilites = new AbilitiesImpl<>();
allAbilites.addAll(super.getAbilities());
allAbilites.addAll(leftHalfCard.getAbilities());
allAbilites.addAll(rightHalfCard.getAbilities());
return allAbilites;
}
public Abilities<Ability> getSharedAbilities(Game game) {
return super.getAbilities(game);
}
@Override
public Abilities<Ability> getAbilities(Game game) {
Abilities<Ability> allAbilites = new AbilitiesImpl<>();
// ignore default spell ability from main card (only halfes are actual)
for (Ability ability : super.getAbilities(game)) {
if (ability instanceof SpellAbility && ((SpellAbility) ability).getSpellAbilityType() == SpellAbilityType.MODAL) {
continue;
}
allAbilites.add(ability);
}
allAbilites.addAll(leftHalfCard.getAbilities(game));
allAbilites.addAll(rightHalfCard.getAbilities(game));
return allAbilites;
}
@Override
public List<String> getRules() {
return new ArrayList<>();
}
@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 int getConvertedManaCost() {
// Rules:
// The converted mana cost of a modal double-faced card is based on the characteristics of the
// face thats 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 getLeftHalfCard().getConvertedManaCost();
}
}

View file

@ -0,0 +1,18 @@
package mage.cards;
import mage.MageInt;
/**
* @author JayDi85
*/
public interface ModalDoubleFacesCardHalf extends Card {
@Override
ModalDoubleFacesCardHalf copy();
void setParentCard(ModalDoubleFacesCard card);
ModalDoubleFacesCard getParentCard();
void setPT(MageInt power, MageInt toughtness);
}

View file

@ -0,0 +1,101 @@
package mage.cards;
import mage.MageInt;
import mage.constants.CardType;
import mage.constants.SpellAbilityType;
import mage.constants.SubType;
import mage.constants.Zone;
import mage.game.Game;
import java.util.List;
import java.util.UUID;
/**
* @author JayDi85
*/
public class ModalDoubleFacesCardHalfImpl extends CardImpl implements ModalDoubleFacesCardHalf {
ModalDoubleFacesCard parentCard;
public ModalDoubleFacesCardHalfImpl(UUID ownerId, CardSetInfo setInfo, CardType[] cardTypes, SubType[] cardSubTypes,
String costs, ModalDoubleFacesCard parentCard, SpellAbilityType spellAbilityType) {
super(ownerId, setInfo, cardTypes, costs, spellAbilityType);
this.parentCard = parentCard;
}
public ModalDoubleFacesCardHalfImpl(final ModalDoubleFacesCardHalfImpl card) {
super(card);
this.parentCard = card.parentCard;
}
@Override
public UUID getOwnerId() {
return parentCard.getOwnerId();
}
@Override
public String getImageName() {
// TODO: own name?
return parentCard.getImageName();
}
@Override
public String getExpansionSetCode() {
// TODO: own set code?
return parentCard.getExpansionSetCode();
}
@Override
public String getCardNumber() {
// TODO: own card number?
return parentCard.getCardNumber();
}
@Override
public boolean moveToZone(Zone toZone, UUID sourceId, Game game, boolean flag, List<UUID> appliedEffects) {
return parentCard.moveToZone(toZone, sourceId, game, flag, appliedEffects);
}
@Override
public boolean moveToExile(UUID exileId, String name, UUID sourceId, Game game, List<UUID> appliedEffects) {
return parentCard.moveToExile(exileId, name, sourceId, game, appliedEffects);
}
@Override
public boolean removeFromZone(Game game, Zone fromZone, UUID sourceId) {
return parentCard.removeFromZone(game, fromZone, sourceId);
}
@Override
public ModalDoubleFacesCard getMainCard() {
return parentCard;
}
@Override
public void setZone(Zone zone, Game game) {
game.setZone(parentCard.getId(), zone);
game.setZone(parentCard.getLeftHalfCard().getId(), zone);
game.setZone(parentCard.getRightHalfCard().getId(), zone);
}
@Override
public ModalDoubleFacesCardHalfImpl copy() {
return new ModalDoubleFacesCardHalfImpl(this);
}
@Override
public void setParentCard(ModalDoubleFacesCard card) {
this.parentCard = card;
}
@Override
public ModalDoubleFacesCard getParentCard() {
return this.parentCard;
}
@Override
public void setPT(MageInt power, MageInt toughness) {
this.power = power;
this.toughness = toughness;
}
}

View file

@ -32,7 +32,6 @@ public abstract class SplitCard extends CardImpl {
String[] names = setInfo.getName().split(" // ");
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;
}
public SplitCard(SplitCard card) {
@ -187,4 +186,15 @@ public abstract class SplitCard extends CardImpl {
rightHalfCard.getAbilities().setControllerId(ownerId);
rightHalfCard.setOwnerId(ownerId);
}
@Override
public int getConvertedManaCost() {
// 202.3d The converted mana cost of a split card not on the stack or of a fused split spell on the
// stack is determined from the combined mana costs of its halves. Otherwise, while a split card is
// on the stack, the converted mana cost of the spell is determined by the mana cost of the half
// that was chosen to be cast. See rule 708, Split Cards.
// split card and it's halfes contains own mana costs, so no need to rewrite logic
return super.getConvertedManaCost();
}
}

View file

@ -1,12 +1,6 @@
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package mage.cards;
/**
*
* @author LevelX2
*/
public interface SplitCardHalf extends Card {

View file

@ -1,8 +1,3 @@
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package mage.cards;
import mage.constants.CardType;
@ -14,7 +9,6 @@ import java.util.List;
import java.util.UUID;
/**
*
* @author LevelX2
*/
public class SplitCardHalfImpl extends CardImpl implements SplitCardHalf {
@ -61,6 +55,11 @@ public class SplitCardHalfImpl extends CardImpl implements SplitCardHalf {
return splitCardParent.moveToExile(exileId, name, sourceId, game, appliedEffects);
}
@Override
public boolean removeFromZone(Game game, Zone fromZone, UUID sourceId) {
return splitCardParent.removeFromZone(game, fromZone, sourceId);
}
@Override
public SplitCard getMainCard() {
return splitCardParent;

View file

@ -20,6 +20,7 @@ import java.util.List;
public class MockCard extends CardImpl {
static public String ADVENTURE_NAME_SEPARATOR = " // ";
static public String MODAL_DOUBLE_FACES_NAME_SEPARATOR = " // ";
// Needs to be here, as it is normally calculated from the
// PlaneswalkerEntersWithLoyaltyAbility of the card... but the MockCard
@ -30,6 +31,7 @@ public class MockCard extends CardImpl {
protected ManaCosts<ManaCost> manaCostLeft;
protected ManaCosts<ManaCost> manaCostRight;
protected String adventureSpellName;
protected String modalDoubleFacesSecondSideName;
public MockCard(CardInfo card) {
super(null, card.getName());
@ -53,7 +55,6 @@ public class MockCard extends CardImpl {
this.frameColor = card.getFrameColor();
this.frameStyle = card.getFrameStyle();
this.splitCard = card.isSplitCard();
this.flipCard = card.isFlipCard();
this.transformable = card.isDoubleFaced();
@ -66,6 +67,10 @@ public class MockCard extends CardImpl {
this.adventureSpellName = card.getAdventureSpellName();
}
if (card.isModalDoubleFacesCard()) {
this.modalDoubleFacesSecondSideName = card.getModalDoubleFacesSecondSideName();
}
if (this.isPlaneswalker()) {
String startingLoyaltyString = card.getStartingLoyalty();
if (startingLoyaltyString.isEmpty()) {
@ -117,8 +122,14 @@ public class MockCard extends CardImpl {
}
public String getFullName(boolean showSecondName) {
if (!showSecondName) {
return getName();
}
if (adventureSpellName != null) {
return getName() + ADVENTURE_NAME_SEPARATOR + adventureSpellName;
} else if (modalDoubleFacesSecondSideName != null) {
return getName() + MODAL_DOUBLE_FACES_NAME_SEPARATOR + modalDoubleFacesSecondSideName;
} else {
return getName();
}

View file

@ -35,7 +35,6 @@ public class MockSplitCard extends SplitCard {
this.usesVariousArt = card.usesVariousArt();
this.color = card.getColor();
this.splitCard = card.isSplitCard();
this.flipCard = card.isFlipCard();
this.transformable = card.isDoubleFaced();

View file

@ -3,8 +3,6 @@ package mage.cards.repository;
import com.j256.ormlite.field.DataType;
import com.j256.ormlite.field.DatabaseField;
import com.j256.ormlite.table.DatabaseTable;
import java.util.*;
import java.util.stream.Collectors;
import mage.ObjectColor;
import mage.abilities.Ability;
import mage.abilities.SpellAbility;
@ -17,6 +15,9 @@ import mage.util.CardUtil;
import mage.util.SubTypeList;
import org.apache.log4j.Logger;
import java.util.*;
import java.util.stream.Collectors;
/**
* @author North
*/
@ -103,6 +104,10 @@ public class CardInfo {
protected boolean adventureCard;
@DatabaseField
protected String adventureSpellName;
@DatabaseField
protected boolean modalDoubleFacesCard;
@DatabaseField
protected String modalDoubleFacesSecondSideName;
public enum ManaCostSide {
LEFT, RIGHT, ALL
@ -121,7 +126,7 @@ public class CardInfo {
this.toughness = card.getToughness().toString();
this.convertedManaCost = card.getConvertedManaCost();
this.rarity = card.getRarity();
this.splitCard = card.isSplitCard();
this.splitCard = card instanceof SplitCard;
this.splitCardFuse = card.getSpellAbility() != null && card.getSpellAbility().getSpellAbilityType() == SpellAbilityType.SPLIT_FUSED;
this.splitCardAftermath = card.getSpellAbility() != null && card.getSpellAbility().getSpellAbilityType() == SpellAbilityType.SPLIT_AFTERMATH;
@ -140,6 +145,11 @@ public class CardInfo {
this.adventureSpellName = ((AdventureCard) card).getSpellCard().getName();
}
if (card instanceof ModalDoubleFacesCard) {
this.modalDoubleFacesCard = true;
this.modalDoubleFacesSecondSideName = ((ModalDoubleFacesCard) card).getRightHalfCard().getName();
}
this.frameStyle = card.getFrameStyle().toString();
this.frameColor = card.getFrameColor(null).toString();
this.variousArt = card.getUsesVariousArt();
@ -153,13 +163,17 @@ public class CardInfo {
this.setSubtypes(card.getSubtype(null).stream().map(SubType::toString).collect(Collectors.toList()));
this.setSuperTypes(card.getSuperType());
// mana cost can contains multiple cards (split left/right, card/adventure)
// mana cost can contains multiple cards (split left/right, modal double faces, card/adventure)
if (card instanceof SplitCard) {
List<String> manaCostLeft = ((SplitCard) card).getLeftHalfCard().getManaCost().getSymbols();
List<String> manaCostRight = ((SplitCard) card).getRightHalfCard().getManaCost().getSymbols();
this.setManaCosts(CardUtil.concatManaSymbols(SPLIT_MANA_SEPARATOR_FULL, manaCostLeft, manaCostRight));
} else if (card instanceof ModalDoubleFacesCard) {
List<String> manaCostLeft = ((ModalDoubleFacesCard) card).getLeftHalfCard().getManaCost().getSymbols();
List<String> manaCostRight = ((ModalDoubleFacesCard) card).getRightHalfCard().getManaCost().getSymbols();
this.setManaCosts(CardUtil.concatManaSymbols(SPLIT_MANA_SEPARATOR_FULL, manaCostLeft, manaCostRight));
} else if (card instanceof AdventureCard) {
List<String> manaCostLeft = ((AdventureCard) card).getSpellCard().getManaCost().getSymbols(); // Spell from left like MTGA
List<String> manaCostLeft = ((AdventureCard) card).getSpellCard().getManaCost().getSymbols();
List<String> manaCostRight = card.getManaCost().getSymbols();
this.setManaCosts(CardUtil.concatManaSymbols(SPLIT_MANA_SEPARATOR_FULL, manaCostLeft, manaCostRight));
} else {
@ -181,6 +195,19 @@ public class CardInfo {
length += rule.length();
rulesList.add(rule);
}
} else if (card instanceof ModalDoubleFacesCard) {
for (String rule : ((ModalDoubleFacesCard) card).getLeftHalfCard().getRules()) {
length += rule.length();
rulesList.add(rule);
}
for (String rule : ((ModalDoubleFacesCard) card).getRightHalfCard().getRules()) {
length += rule.length();
rulesList.add(rule);
}
for (String rule : card.getRules()) {
length += rule.length();
rulesList.add(rule);
}
} else {
for (String rule : card.getRules()) {
length += rule.length();
@ -222,7 +249,6 @@ public class CardInfo {
}
}
if (this.startingLoyalty == null) {
//Logger.getLogger(CardInfo.class).warn("Planeswalker `" + card.getName() + "` missing starting loyalty");
this.startingLoyalty = "";
}
} else {
@ -447,4 +473,12 @@ public class CardInfo {
public String getAdventureSpellName() {
return adventureSpellName;
}
public boolean isModalDoubleFacesCard() {
return modalDoubleFacesCard;
}
public String getModalDoubleFacesSecondSideName() {
return modalDoubleFacesSecondSideName;
}
}

View file

@ -34,9 +34,9 @@ public enum CardRepository {
private static final String JDBC_URL = "jdbc:h2:file:./db/cards.h2;AUTO_SERVER=TRUE";
private static final String VERSION_ENTITY_NAME = "card";
// raise this if db structure was changed
private static final long CARD_DB_VERSION = 52;
private static final long CARD_DB_VERSION = 53;
// raise this if new cards were added to the server
private static final long CARD_CONTENT_VERSION = 232;
private static final long CARD_CONTENT_VERSION = 233;
private Dao<CardInfo, Object> cardDao;
private Set<String> classNames;
private final RepositoryEventSource eventSource = new RepositoryEventSource();

View file

@ -59,7 +59,7 @@ public final class CardScanner {
new CardSetInfo(setInfo.getName(), set.getCode(), setInfo.getCardNumber(), setInfo.getRarity(), setInfo.getGraphicInfo()),
errorsList);
if (card != null) {
cardsToAdd.add(new CardInfo(card));
cardsToAdd.add(new CardInfo(card)); // normal, transformed, adventure, modal double faces -- all must have single face in db
if (card instanceof SplitCard) {
SplitCard splitCard = (SplitCard) card;
cardsToAdd.add(new CardInfo(splitCard.getLeftHalfCard()));

View file

@ -1,7 +1,6 @@
package mage.constants;
/**
*
* @author North
*/
public enum SpellAbilityType {
@ -13,7 +12,9 @@ public enum SpellAbilityType {
SPLIT_FUSED("Split SpellAbility"),
SPLIT_LEFT("LeftSplit SpellAbility"),
SPLIT_RIGHT("RightSplit SpellAbility"),
MODE("Mode SpellAbility"),
MODAL("Modal SpellAbility"), // used for modal double faces cards
MODAL_LEFT("LeftModal SpellAbility"),
MODAL_RIGHT("RightModal SpellAbility"),
SPLICE("Spliced SpellAbility"),
ADVENTURE_SPELL("Adventure SpellAbility");

View file

@ -1,7 +1,7 @@
package mage.filter.predicate.mageobject;
import mage.MageObject;
import mage.cards.ModalDoubleFacesCardHalf;
import mage.cards.SplitCardHalf;
import mage.constants.Zone;
import mage.filter.predicate.Predicate;
@ -18,8 +18,13 @@ public enum MulticoloredPredicate implements Predicate<MageObject> {
// 708.3. Each split card that consists of two halves with different colored mana symbols in their mana costs
// is a multicolored card while it's not a spell on the stack. While it's a spell on the stack, it's only the
// color or colors of the half or halves being cast. #
if (input instanceof SplitCardHalf && game.getState().getZone(input.getId()) != Zone.STACK) {
if (input instanceof SplitCardHalf
&& game.getState().getZone(input.getId()) != Zone.STACK) {
return 1 < ((SplitCardHalf) input).getMainCard().getColor(game).getColorCount();
} else if (input instanceof ModalDoubleFacesCardHalf
&& (game.getState().getZone(input.getId()) != Zone.STACK && game.getState().getZone(input.getId()) != Zone.BATTLEFIELD)) {
// While a double-faced card isnt on the stack or battlefield, consider only the characteristics of its front face.
return 1 < ((ModalDoubleFacesCardHalf) input).getMainCard().getColor(game).getColorCount();
} else {
return 1 < input.getColor(game).getColorCount();
}

View file

@ -1,6 +1,7 @@
package mage.filter.predicate.mageobject;
import mage.MageObject;
import mage.cards.ModalDoubleFacesCard;
import mage.cards.SplitCard;
import mage.constants.SpellAbilityType;
import mage.filter.predicate.Predicate;
@ -32,10 +33,15 @@ public class NamePredicate implements Predicate<MageObject> {
}
// If a player names a card, the player may name either half of a split card, but not both.
// A split card has the chosen name if one of its two names matches the chosen name.
// Same for modal double faces cards
if (input instanceof SplitCard) {
return CardUtil.haveSameNames(name, ((SplitCard) input).getLeftHalfCard().getName(), this.ignoreMtgRuleForEmptyNames) ||
CardUtil.haveSameNames(name, ((SplitCard) input).getRightHalfCard().getName(), this.ignoreMtgRuleForEmptyNames) ||
CardUtil.haveSameNames(name, input.getName(), this.ignoreMtgRuleForEmptyNames);
} else if (input instanceof ModalDoubleFacesCard) {
return CardUtil.haveSameNames(name, ((ModalDoubleFacesCard) input).getLeftHalfCard().getName(), this.ignoreMtgRuleForEmptyNames) ||
CardUtil.haveSameNames(name, ((ModalDoubleFacesCard) input).getRightHalfCard().getName(), this.ignoreMtgRuleForEmptyNames) ||
CardUtil.haveSameNames(name, input.getName(), this.ignoreMtgRuleForEmptyNames);
} else if (input instanceof Spell && ((Spell) input).getSpellAbility().getSpellAbilityType() == SpellAbilityType.SPLIT_FUSED) {
SplitCard card = (SplitCard) ((Spell) input).getCard();
return CardUtil.haveSameNames(name, card.getLeftHalfCard().getName(), this.ignoreMtgRuleForEmptyNames) ||

View file

@ -2,6 +2,7 @@ package mage.filter.predicate.other;
import mage.cards.AdventureCard;
import mage.cards.Card;
import mage.cards.ModalDoubleFacesCard;
import mage.cards.SplitCard;
import mage.cards.mock.MockCard;
import mage.constants.SubType;
@ -52,6 +53,8 @@ public class CardTextPredicate implements Predicate<Card> {
String fullName = input.getName();
if (input instanceof MockCard) {
fullName = ((MockCard) input).getFullName(true);
} else if (input instanceof ModalDoubleFacesCard) {
fullName = input.getName() + MockCard.MODAL_DOUBLE_FACES_NAME_SEPARATOR + ((ModalDoubleFacesCard) input).getRightHalfCard().getName();
} else if (input instanceof AdventureCard) {
fullName = input.getName() + MockCard.ADVENTURE_NAME_SEPARATOR + ((AdventureCard) input).getSpellCard().getName();
}
@ -67,14 +70,14 @@ public class CardTextPredicate implements Predicate<Card> {
}
}
//separate by spaces
// separate by spaces
String[] tokens = text.toLowerCase(Locale.ENGLISH).split(" ");
for (String token : tokens) {
boolean found = false;
if (!token.isEmpty()) {
// then try to find in rules
if (inRules) {
if (input.isSplitCard()) {
if (input instanceof SplitCard) {
for (String rule : ((SplitCard) input).getLeftHalfCard().getRules(game)) {
if (rule.toLowerCase(Locale.ENGLISH).contains(token)) {
found = true;
@ -88,6 +91,22 @@ public class CardTextPredicate implements Predicate<Card> {
}
}
}
if (input instanceof ModalDoubleFacesCard) {
for (String rule : ((ModalDoubleFacesCard) input).getLeftHalfCard().getRules(game)) {
if (rule.toLowerCase(Locale.ENGLISH).contains(token)) {
found = true;
break;
}
}
for (String rule : ((ModalDoubleFacesCard) input).getRightHalfCard().getRules(game)) {
if (rule.toLowerCase(Locale.ENGLISH).contains(token)) {
found = true;
break;
}
}
}
if (input instanceof AdventureCard) {
for (String rule : ((AdventureCard) input).getSpellCard().getRules(game)) {
if (rule.toLowerCase(Locale.ENGLISH).contains(token)) {
@ -96,6 +115,7 @@ public class CardTextPredicate implements Predicate<Card> {
}
}
}
for (String rule : input.getRules(game)) {
if (rule.toLowerCase(Locale.ENGLISH).contains(token)) {
found = true;

View file

@ -244,17 +244,29 @@ public abstract class GameImpl implements Game, Serializable {
card.setOwnerId(ownerId);
gameCards.put(card.getId(), card);
state.addCard(card);
if (card.isSplitCard()) {
if (card instanceof SplitCard) {
// left
Card leftCard = ((SplitCard) card).getLeftHalfCard();
leftCard.setOwnerId(ownerId);
gameCards.put(leftCard.getId(), leftCard);
state.addCard(leftCard);
// right
Card rightCard = ((SplitCard) card).getRightHalfCard();
rightCard.setOwnerId(ownerId);
gameCards.put(rightCard.getId(), rightCard);
state.addCard(rightCard);
}
if (card instanceof AdventureCard) {
} else if (card instanceof ModalDoubleFacesCard) {
// left
Card leftCard = ((ModalDoubleFacesCard) card).getLeftHalfCard();
leftCard.setOwnerId(ownerId);
gameCards.put(leftCard.getId(), leftCard);
state.addCard(leftCard);
// right
Card rightCard = ((ModalDoubleFacesCard) card).getRightHalfCard();
rightCard.setOwnerId(ownerId);
gameCards.put(rightCard.getId(), rightCard);
state.addCard(rightCard);
} else if (card instanceof AdventureCard) {
Card spellCard = ((AdventureCard) card).getSpellCard();
spellCard.setOwnerId(ownerId);
gameCards.put(spellCard.getId(), spellCard);
@ -1911,7 +1923,7 @@ public abstract class GameImpl implements Game, Serializable {
Iterator<Card> copiedCards = this.getState().getCopiedCards().iterator();
while (copiedCards.hasNext()) {
Card card = copiedCards.next();
if (card instanceof SplitCardHalf || card instanceof AdventureCardSpell) {
if (card instanceof SplitCardHalf || card instanceof AdventureCardSpell || card instanceof ModalDoubleFacesCardHalf) {
continue; // only the main card is moves, not the halves (cause halfes is not copied - it uses original card -- TODO: need to fix (bugs with same card copy)?
}
Zone zone = state.getZone(card.getId());

View file

@ -1,9 +1,5 @@
package mage.game;
import java.io.Serializable;
import java.util.*;
import static java.util.Collections.emptyList;
import java.util.stream.Collectors;
import mage.MageObject;
import mage.MageObjectReference;
import mage.abilities.*;
@ -12,6 +8,7 @@ import mage.abilities.effects.ContinuousEffects;
import mage.abilities.effects.Effect;
import mage.cards.AdventureCard;
import mage.cards.Card;
import mage.cards.ModalDoubleFacesCard;
import mage.cards.SplitCard;
import mage.constants.Zone;
import mage.designations.Designation;
@ -40,6 +37,12 @@ import mage.util.ThreadLocalStringBuilder;
import mage.watchers.Watcher;
import mage.watchers.Watchers;
import java.io.Serializable;
import java.util.*;
import java.util.stream.Collectors;
import static java.util.Collections.emptyList;
/**
* @author BetaSteward_at_googlemail.com
* <p>
@ -626,6 +629,7 @@ public class GameState implements Serializable, Copyable<GameState> {
// public void addMessage(String message) {
// this.messages.add(message);
// }
/**
* Returns a list of all players of the game ignoring range or if a player
* has lost or left the game.
@ -799,7 +803,7 @@ public class GameState implements Serializable, Copyable<GameState> {
for (Map.Entry<ZoneChangeData, List<GameEvent>> entry : eventsByKey.entrySet()) {
Set<Card> movedCards = new LinkedHashSet<>();
Set<PermanentToken> movedTokens = new LinkedHashSet<>();
for (Iterator<GameEvent> it = entry.getValue().iterator(); it.hasNext();) {
for (Iterator<GameEvent> it = entry.getValue().iterator(); it.hasNext(); ) {
GameEvent event = it.next();
ZoneChangeEvent castEvent = (ZoneChangeEvent) event;
UUID targetId = castEvent.getTargetId();
@ -835,10 +839,14 @@ public class GameState implements Serializable, Copyable<GameState> {
}
// TODO Watchers?
// TODO Abilities?
if (card.isSplitCard()) {
if (card instanceof SplitCard) {
removeCopiedCard(((SplitCard) card).getLeftHalfCard());
removeCopiedCard(((SplitCard) card).getRightHalfCard());
}
if (card instanceof ModalDoubleFacesCard) {
removeCopiedCard(((ModalDoubleFacesCard) card).getLeftHalfCard());
removeCopiedCard(((ModalDoubleFacesCard) card).getRightHalfCard());
}
if (card instanceof AdventureCard) {
removeCopiedCard(((AdventureCard) card).getSpellCard());
}
@ -1194,21 +1202,34 @@ public class GameState implements Serializable, Copyable<GameState> {
}
public Card copyCard(Card cardToCopy, Ability source, Game game) {
// main card
Card copiedCard = cardToCopy.copy();
copiedCard.assignNewId();
copiedCard.setOwnerId(source.getControllerId());
copiedCard.setCopy(true, cardToCopy);
copiedCards.put(copiedCard.getId(), copiedCard);
addCard(copiedCard);
if (copiedCard.isSplitCard()) {
// other faces
if (copiedCard instanceof SplitCard) {
// left
Card leftCard = ((SplitCard) copiedCard).getLeftHalfCard(); // TODO: must be new ID (bugs with same card copy)?
copiedCards.put(leftCard.getId(), leftCard);
addCard(leftCard);
// right
Card rightCard = ((SplitCard) copiedCard).getRightHalfCard();
copiedCards.put(rightCard.getId(), rightCard);
addCard(rightCard);
}
if (copiedCard instanceof AdventureCard) {
} else if (copiedCard instanceof ModalDoubleFacesCard) {
// left
Card leftCard = ((ModalDoubleFacesCard) copiedCard).getLeftHalfCard(); // TODO: must be new ID (bugs with same card copy)?
copiedCards.put(leftCard.getId(), leftCard);
addCard(leftCard);
// right
Card rightCard = ((ModalDoubleFacesCard) copiedCard).getRightHalfCard();
copiedCards.put(rightCard.getId(), rightCard);
addCard(rightCard);
} else if (copiedCard instanceof AdventureCard) {
Card spellCard = ((AdventureCard) copiedCard).getSpellCard();
copiedCards.put(spellCard.getId(), spellCard);
addCard(spellCard);

View file

@ -1,9 +1,7 @@
package mage.game;
import mage.cards.Card;
import mage.cards.Cards;
import mage.cards.CardsImpl;
import mage.cards.MeldCard;
import mage.abilities.keyword.TransformAbility;
import mage.cards.*;
import mage.constants.Outcome;
import mage.constants.Zone;
import mage.filter.FilterCard;
@ -19,7 +17,6 @@ import mage.players.Player;
import mage.target.TargetCard;
import java.util.*;
import mage.abilities.keyword.TransformAbility;
/**
* Created by samuelsandeen on 9/6/16.
@ -54,7 +51,7 @@ public final class ZonesHandler {
public static List<ZoneChangeInfo> moveCards(List<ZoneChangeInfo> zoneChangeInfos, Game game) {
// Handle Unmelded Meld Cards
for (ListIterator<ZoneChangeInfo> itr = zoneChangeInfos.listIterator(); itr.hasNext();) {
for (ListIterator<ZoneChangeInfo> itr = zoneChangeInfos.listIterator(); itr.hasNext(); ) {
ZoneChangeInfo info = itr.next();
MeldCard card = game.getMeldCard(info.event.getTargetId());
// Copies should be handled as normal cards.
@ -106,6 +103,10 @@ public final class ZonesHandler {
if (!(targetCard instanceof Permanent) && targetCard != null) {
if (targetCard instanceof MeldCard) {
cards = ((MeldCard) targetCard).getHalves();
} else if (targetCard instanceof ModalDoubleFacesCard) {
cards = new CardsImpl(targetCard);
cards.add(((ModalDoubleFacesCard) targetCard).getLeftHalfCard());
cards.add(((ModalDoubleFacesCard) targetCard).getRightHalfCard());
} else {
cards = new CardsImpl(targetCard);
}
@ -174,14 +175,19 @@ public final class ZonesHandler {
throw new UnsupportedOperationException("to Zone " + toZone.toString() + " not supported yet");
}
}
game.setZone(event.getTargetId(), event.getToZone());
if (targetCard instanceof MeldCard && cards != null) {
if (event.getToZone() != Zone.BATTLEFIELD) {
((MeldCard) targetCard).setMelded(false, game);
}
if (cards != null && (targetCard instanceof MeldCard || targetCard instanceof ModalDoubleFacesCard)) {
// update other parts too
for (Card card : cards.getCards(game)) {
game.setZone(card.getId(), event.getToZone());
}
// reset meld status
if (targetCard instanceof MeldCard) {
if (event.getToZone() != Zone.BATTLEFIELD) {
((MeldCard) targetCard).setMelded(false, game);
}
}
}
}
@ -203,7 +209,7 @@ public final class ZonesHandler {
if (info instanceof ZoneChangeInfo.Unmelded) {
ZoneChangeInfo.Unmelded unmelded = (ZoneChangeInfo.Unmelded) info;
MeldCard meld = game.getMeldCard(info.event.getTargetId());
for (Iterator<ZoneChangeInfo> itr = unmelded.subInfo.iterator(); itr.hasNext();) {
for (Iterator<ZoneChangeInfo> itr = unmelded.subInfo.iterator(); itr.hasNext(); ) {
ZoneChangeInfo subInfo = itr.next();
if (!maybeRemoveFromSourceZone(subInfo, game)) {
itr.remove();
@ -232,10 +238,10 @@ public final class ZonesHandler {
if (info.faceDown) {
card.setFaceDown(true, game);
} else if (info.event.getToZone().equals(Zone.BATTLEFIELD)) {
if (!card.isPermanent()
if (!card.isPermanent()
&& (!card.isTransformable() || Boolean.FALSE.equals(game.getState().getValue(TransformAbility.VALUE_KEY_ENTER_TRANSFORMED + card.getId())))) {
// Non permanents (Instants, Sorceries, ... stay in the zone they are if an abilty/effect tries to move it to the battlefield
return false;
return false;
}
}
if (!game.replaceEvent(event)) {
@ -248,9 +254,10 @@ public final class ZonesHandler {
Permanent permanent;
if (card instanceof MeldCard) {
permanent = new PermanentMeld(card, event.getPlayerId(), game);
} else if (card instanceof ModalDoubleFacesCard) {
throw new IllegalStateException("Try to move mdf card to battlefield instead half");
} else if (card instanceof Permanent) {
// This should never happen.
permanent = (Permanent) card;
throw new IllegalStateException("Try to move permanent card to battlefield");
} else {
permanent = new PermanentCard(card, event.getPlayerId(), game);
}

View file

@ -712,11 +712,6 @@ public class Spell extends StackObjImpl implements Card {
return null;
}
@Override
public boolean isSplitCard() {
return false;
}
@Override
public boolean isTransformable() {
return false;

View file

@ -1602,6 +1602,10 @@ public abstract class PlayerImpl implements Player, Serializable {
needId1 = object.getId();
needId2 = ((SplitCard) object).getLeftHalfCard().getId();
needId3 = ((SplitCard) object).getRightHalfCard().getId();
} else if (object instanceof ModalDoubleFacesCard) {
needId1 = object.getId();
needId2 = ((ModalDoubleFacesCard) object).getLeftHalfCard().getId();
needId3 = ((ModalDoubleFacesCard) object).getRightHalfCard().getId();
} else if (object instanceof AdventureCard) {
needId1 = object.getId();
needId2 = ((AdventureCard) object).getMainCard().getId();
@ -3402,10 +3406,16 @@ public abstract class PlayerImpl implements Player, Serializable {
// BASIC abilities
if (object instanceof SplitCard) {
SplitCard splitCard = (SplitCard) object;
getPlayableFromObjectSingle(game, fromZone, splitCard.getLeftHalfCard(), splitCard.getLeftHalfCard().getAbilities(game), availableMana, output);
getPlayableFromObjectSingle(game, fromZone, splitCard.getRightHalfCard(), splitCard.getRightHalfCard().getAbilities(game), availableMana, output);
getPlayableFromObjectSingle(game, fromZone, splitCard, splitCard.getSharedAbilities(game), availableMana, output);
SplitCard mainCard = (SplitCard) object;
getPlayableFromObjectSingle(game, fromZone, mainCard.getLeftHalfCard(), mainCard.getLeftHalfCard().getAbilities(game), availableMana, output);
getPlayableFromObjectSingle(game, fromZone, mainCard.getRightHalfCard(), mainCard.getRightHalfCard().getAbilities(game), availableMana, output);
getPlayableFromObjectSingle(game, fromZone, mainCard, mainCard.getSharedAbilities(game), availableMana, output);
}
if (object instanceof ModalDoubleFacesCard) {
ModalDoubleFacesCard mainCard = (ModalDoubleFacesCard) object;
getPlayableFromObjectSingle(game, fromZone, mainCard.getLeftHalfCard(), mainCard.getLeftHalfCard().getAbilities(game), availableMana, output);
getPlayableFromObjectSingle(game, fromZone, mainCard.getRightHalfCard(), mainCard.getRightHalfCard().getAbilities(game), availableMana, output);
getPlayableFromObjectSingle(game, fromZone, mainCard, mainCard.getSharedAbilities(game), availableMana, output);
} else if (object instanceof AdventureCard) {
// adventure must use different card characteristics for different spells (main or adventure)
AdventureCard adventureCard = (AdventureCard) object;

View file

@ -1,11 +1,5 @@
package mage.util;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.stream.Collectors;
import mage.MageObject;
import mage.Mana;
import mage.abilities.Abilities;
@ -17,6 +11,8 @@ import mage.abilities.costs.mana.*;
import mage.abilities.effects.ContinuousEffect;
import mage.cards.Card;
import mage.cards.MeldCard;
import mage.cards.ModalDoubleFacesCard;
import mage.cards.SplitCard;
import mage.constants.*;
import mage.filter.Filter;
import mage.filter.predicate.mageobject.NamePredicate;
@ -31,6 +27,13 @@ import mage.players.Player;
import mage.target.Target;
import mage.util.functions.CopyTokenFunction;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.stream.Collectors;
/**
* @author nantuko
*/
@ -39,10 +42,10 @@ public final class CardUtil {
private static final String SOURCE_EXILE_ZONE_TEXT = "SourceExileZone";
static final String[] numberStrings = {"zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine",
"ten", "eleven", "twelve", "thirteen", "fourteen", "fifteen", "sixteen", "seventeen", "eighteen", "nineteen", "twenty"};
"ten", "eleven", "twelve", "thirteen", "fourteen", "fifteen", "sixteen", "seventeen", "eighteen", "nineteen", "twenty"};
static final String[] ordinalStrings = {"first", "second", "third", "fourth", "fifth", "sixth", "seventh", "eightth", "ninth",
"tenth", "eleventh", "twelfth", "thirteenth", "fourteenth", "fifteenth", "sixteenth", "seventeenth", "eighteenth", "nineteenth", "twentieth"};
"tenth", "eleventh", "twelfth", "thirteenth", "fourteenth", "fifteenth", "sixteenth", "seventeenth", "eighteenth", "nineteenth", "twentieth"};
public static final SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss.SSS");
@ -877,4 +880,21 @@ public final class CardUtil {
}
}
}
/**
* Return card name for same name searching
*
* @param card
* @return
*/
public static String getCardNameForSameNameSearch(Card card) {
// it's ok to return one name only cause NamePredicate can find same card by first name
if (card instanceof SplitCard) {
return ((SplitCard) card).getLeftHalfCard().getName();
} else if (card instanceof ModalDoubleFacesCard) {
return ((ModalDoubleFacesCard) card).getLeftHalfCard().getName();
} else {
return card.getName();
}
}
}