mirror of
https://github.com/magefree/mage.git
synced 2025-12-20 02:30:08 -08:00
Merge pull request #14061
* move setPT to Card * Create DoubleFacedCard and DoubleFacedCardHalf to share code between … * Create Transforming Double Face Card class * allow putting either permanent side of a double faced card to the bat… * refactor exile and return transforming card * update ModalDoubleFacedCard references to DoubleFacedCard where relev… * update for GUI * refactor a disturb card * refactor more disturb cards for test coverage * refactor a transform card * refactor more transform cards for test coverage * fix Archangel Avacyn * fix cantPlayTDFCBackSide inconsistency * fix Double Faced Cards having triggers and static abilities when tran… * fix Double Faced Cards card view erroring when flipping in client * fix test_Copy_AsSpell_Backside inconsistency * enable Spider-Man MDFC * convert TDFC with saga as the front and add card references to Transf… * refactor More Than Meets the Eye Card * refactor a battle * refactor a craft card * update comment on PeterParkerTest * Merge branch 'master' into rework-dfc * fix Saga TDFC Azusa's Many Journeys * fix double faced cards adding permanent triggers / effects to game * move permanents entering map into Battlefield * convert Room cards for new Permanent structure * fix disturb not exiling * Merge branch 'master' into rework-dfc * fix Eddie Brock Power/Toughness * fix Miles Morales ability on main card * fix verify conditions for siege and day/night cards * change room characteristics to text effect to match game rules * update verify test to skip DoubleFacedCard in missing card test * accidentally removed transform condition * Merge branch 'master' into rework-dfc * fix verify * CardUtil - remove unnecessary line from castSingle method
This commit is contained in:
parent
29557f4334
commit
69e20b1061
121 changed files with 3020 additions and 2225 deletions
|
|
@ -23,7 +23,7 @@ public class EntersBattlefieldTriggeredAbility extends TriggeredAbilityImpl {
|
|||
}
|
||||
|
||||
public EntersBattlefieldTriggeredAbility(Effect effect, boolean optional) {
|
||||
super(Zone.ALL, effect, optional); // Zone.All because a creature with trigger can be put into play and be sacrificed during the resolution of an effect (discard Obstinate Baloth with Smallpox)
|
||||
super(Zone.BATTLEFIELD, effect, optional); // Zone.All doesn't appear to be necessary anymore (discard Obstinate Baloth with Smallpox still works)
|
||||
this.withRuleTextReplacement(true); // default true to replace "{this}" with "it" or "this creature"
|
||||
|
||||
// warning, it's impossible to add text auto-replacement for creatures here (When this creature enters),
|
||||
|
|
|
|||
|
|
@ -17,6 +17,9 @@ import mage.game.events.GameEvent;
|
|||
import mage.game.events.ZoneChangeEvent;
|
||||
import mage.game.stack.Spell;
|
||||
import mage.players.Player;
|
||||
import mage.util.CardUtil;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* @author LevelX2
|
||||
|
|
@ -99,11 +102,10 @@ class PutIntoGraveFromAnywhereEffect extends ReplacementEffectImpl {
|
|||
|
||||
@Override
|
||||
public boolean applies(GameEvent event, Ability source, Game game) {
|
||||
UUID cardId = CardUtil.getMainCardId(game, source.getSourceId()); // for split cards
|
||||
if (((ZoneChangeEvent) event).getToZone() == Zone.GRAVEYARD
|
||||
&& event.getTargetId().equals(source.getSourceId())) {
|
||||
if (condition == null || condition.apply(game, source)) {
|
||||
return true;
|
||||
}
|
||||
&& (event.getTargetId().equals(cardId) || event.getTargetId().equals(source.getSourceId()))) {
|
||||
return condition == null || condition.apply(game, source);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
|
|
|||
39
Mage/src/main/java/mage/abilities/common/RoomAbility.java
Normal file
39
Mage/src/main/java/mage/abilities/common/RoomAbility.java
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
package mage.abilities.common;
|
||||
|
||||
import mage.abilities.effects.common.RoomCharacteristicsEffect;
|
||||
import mage.constants.Zone;
|
||||
import mage.game.Game;
|
||||
import mage.game.permanent.Permanent;
|
||||
import mage.game.permanent.PermanentImpl;
|
||||
|
||||
// For the overall Room card flavor text and mana value effect.
|
||||
public class RoomAbility extends SimpleStaticAbility {
|
||||
public RoomAbility() {
|
||||
super(Zone.BATTLEFIELD, new RoomCharacteristicsEffect());
|
||||
this.setRuleVisible(true);
|
||||
this.setRuleAtTheTop(true);
|
||||
}
|
||||
|
||||
protected RoomAbility(final RoomAbility ability) {
|
||||
super(ability);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRule() {
|
||||
return "<i>(You may cast either half. That door unlocks on the battlefield. " +
|
||||
"As a sorcery, you may pay the mana cost of a locked door to unlock it.)</i>";
|
||||
}
|
||||
|
||||
@Override
|
||||
public RoomAbility copy() {
|
||||
return new RoomAbility(this);
|
||||
}
|
||||
|
||||
public void applyCharacteristics(Game game, Permanent permanent) {
|
||||
((RoomCharacteristicsEffect) this.getEffects().get(0)).removeCharacteristics(game, permanent);
|
||||
}
|
||||
|
||||
public void restoreUnlockedStats(Game game, PermanentImpl permanent) {
|
||||
((RoomCharacteristicsEffect) this.getEffects().get(0)).restoreUnlockedStats(game, permanent);
|
||||
}
|
||||
}
|
||||
|
|
@ -12,12 +12,11 @@ import mage.game.Game;
|
|||
import mage.game.permanent.Permanent;
|
||||
|
||||
/**
|
||||
* Special action for Room cards to unlock a locked half by paying its
|
||||
* mana cost.
|
||||
* This ability is only present if the corresponding half is currently
|
||||
* locked.
|
||||
* @author oscscull
|
||||
* Special action for Room cards to unlock a locked half by paying its
|
||||
* mana
|
||||
* cost.
|
||||
* This ability is only present if the corresponding half is currently
|
||||
* locked.
|
||||
*/
|
||||
public class RoomUnlockAbility extends SpecialAction {
|
||||
|
||||
|
|
@ -61,6 +60,10 @@ public class RoomUnlockAbility extends SpecialAction {
|
|||
sb.append(isLeftHalf ? "left" : "right").append(" half is locked.)</i>");
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public boolean isLeftHalf() {
|
||||
return isLeftHalf;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import mage.abilities.costs.mana.ManaCostsImpl;
|
|||
import mage.abilities.effects.ContinuousEffectImpl;
|
||||
import mage.abilities.keyword.TransformAbility;
|
||||
import mage.cards.Card;
|
||||
import mage.cards.TransformingDoubleFacedCard;
|
||||
import mage.constants.*;
|
||||
import mage.game.Game;
|
||||
import mage.game.stack.Spell;
|
||||
|
|
@ -20,6 +21,7 @@ import java.util.UUID;
|
|||
public class SpellTransformedAbility extends SpellAbility {
|
||||
|
||||
protected final String manaCost; //This variable is only used for rules text
|
||||
private boolean ignoreTransformEffect; // TODO: temporary while converting tdfc
|
||||
|
||||
public SpellTransformedAbility(Card card, String manaCost) {
|
||||
super(card.getSecondFaceSpellAbility());
|
||||
|
|
@ -35,7 +37,11 @@ public class SpellTransformedAbility extends SpellAbility {
|
|||
this.clearManaCosts();
|
||||
this.clearManaCostsToPay();
|
||||
this.addCost(new ManaCostsImpl<>(manaCost));
|
||||
this.addSubAbility(new TransformAbility());
|
||||
if (!(card instanceof TransformingDoubleFacedCard)) {
|
||||
this.addSubAbility(new TransformAbility());
|
||||
} else {
|
||||
ignoreTransformEffect = true;
|
||||
}
|
||||
}
|
||||
|
||||
public SpellTransformedAbility(final SpellAbility ability) {
|
||||
|
|
@ -54,6 +60,7 @@ public class SpellTransformedAbility extends SpellAbility {
|
|||
protected SpellTransformedAbility(final SpellTransformedAbility ability) {
|
||||
super(ability);
|
||||
this.manaCost = ability.manaCost;
|
||||
this.ignoreTransformEffect = ability.ignoreTransformEffect;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -65,6 +72,9 @@ public class SpellTransformedAbility extends SpellAbility {
|
|||
public boolean activate(Game game, Set<MageIdentifier> allowedIdentifiers, boolean noMana) {
|
||||
if (super.activate(game, allowedIdentifiers, noMana)) {
|
||||
game.getState().setValue(TransformAbility.VALUE_KEY_ENTER_TRANSFORMED + getSourceId(), Boolean.TRUE);
|
||||
if (ignoreTransformEffect) {
|
||||
return true;
|
||||
}
|
||||
// TODO: must be removed after transform cards (one side) migrated to MDF engine (multiple sides)
|
||||
TransformedEffect effect = new TransformedEffect();
|
||||
game.addEffect(effect, this);
|
||||
|
|
|
|||
|
|
@ -3,10 +3,7 @@ package mage.abilities.effects;
|
|||
import mage.MageIdentifier;
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.ActivatedAbility;
|
||||
import mage.cards.Card;
|
||||
import mage.cards.ModalDoubleFacedCard;
|
||||
import mage.cards.SplitCard;
|
||||
import mage.cards.CardWithSpellOption;
|
||||
import mage.cards.*;
|
||||
import mage.constants.*;
|
||||
import mage.game.Game;
|
||||
import mage.players.Player;
|
||||
|
|
@ -92,9 +89,9 @@ public abstract class AsThoughEffectImpl extends ContinuousEffectImpl implements
|
|||
player.setCastSourceIdWithAlternateMana(leftCard.getId(), null, leftCard.getSpellAbility().getCosts(), identifier);
|
||||
Card rightCard = ((SplitCard) card).getRightHalfCard();
|
||||
player.setCastSourceIdWithAlternateMana(rightCard.getId(), null, rightCard.getSpellAbility().getCosts(), identifier);
|
||||
} else if (card instanceof ModalDoubleFacedCard) {
|
||||
Card leftCard = ((ModalDoubleFacedCard) card).getLeftHalfCard();
|
||||
Card rightCard = ((ModalDoubleFacedCard) card).getRightHalfCard();
|
||||
} else if (card instanceof DoubleFacedCard) {
|
||||
Card leftCard = ((DoubleFacedCard) card).getLeftHalfCard();
|
||||
Card rightCard = ((DoubleFacedCard) card).getRightHalfCard();
|
||||
// some MDFC's are land. IE: sea gate restoration
|
||||
if (!leftCard.isLand(game)) {
|
||||
player.setCastSourceIdWithAlternateMana(leftCard.getId(), null, leftCard.getSpellAbility().getCosts(), identifier);
|
||||
|
|
|
|||
|
|
@ -2,9 +2,11 @@ package mage.abilities.effects.common;
|
|||
|
||||
import mage.MageObject;
|
||||
import mage.MageObjectReference;
|
||||
import mage.abilities.Abilities;
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.effects.ContinuousEffectImpl;
|
||||
import mage.cards.Card;
|
||||
import mage.abilities.common.RoomAbility;
|
||||
import mage.constants.*;
|
||||
import mage.game.Game;
|
||||
import mage.game.permanent.Permanent;
|
||||
|
|
@ -63,6 +65,16 @@ public class CopyEffect extends ContinuousEffectImpl {
|
|||
permanent = game.getPermanentEntering(copyToObjectId);
|
||||
if (permanent != null) {
|
||||
copyToPermanent(permanent, game, source);
|
||||
// Apply Room characteristics since effects aren't applied to entering permanents yet
|
||||
if (permanent.hasSubtype(SubType.ROOM, game)) {
|
||||
Abilities<Ability> abilities = permanent.getAbilities();
|
||||
for (Ability ability : abilities) {
|
||||
if (ability instanceof RoomAbility) {
|
||||
((RoomAbility) ability).applyCharacteristics(game, permanent);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
// set reference to the permanent later on the battlefield so we have to add already one (if no token) to the zone change counter
|
||||
int ZCCDiff = 1;
|
||||
if (permanent instanceof PermanentToken) {
|
||||
|
|
|
|||
|
|
@ -216,6 +216,28 @@ public class CreateTokenCopyTargetEffect extends OneShotEffect {
|
|||
// create token and modify all attributes permanently (without game usage)
|
||||
Token token = CopyTokenFunction.createTokenCopy(copyFrom, game); // needed so that entersBattlefield triggered abilities see the attributes (e.g. Master Biomancer)
|
||||
applier.apply(game, token, source, targetId);
|
||||
// the active face should have the modified attributes
|
||||
if (token.isEntersTransformed()) {
|
||||
applyAdditionsToToken(token.getBackFace());
|
||||
} else {
|
||||
applyAdditionsToToken(token);
|
||||
}
|
||||
|
||||
token.putOntoBattlefield(number, game, source, playerId == null ? source.getControllerId() : playerId, tapped, attacking, attackedPlayer, attachedTo);
|
||||
for (UUID tokenId : token.getLastAddedTokenIds()) { // by cards like Doubling Season multiple tokens can be added to the battlefield
|
||||
Permanent tokenPermanent = game.getPermanent(tokenId);
|
||||
if (tokenPermanent != null) {
|
||||
addedTokenPermanents.add(tokenPermanent);
|
||||
// TODO: Workaround to add counters to all created tokens, necessary for correct interactions with cards like Chatterfang, Squirrel General and Ochre Jelly / Printlifter Ooze. See #10786
|
||||
if (counter != null && numberOfCounters > 0) {
|
||||
tokenPermanent.addCounters(counter.createInstance(numberOfCounters), source.getControllerId(), source, game);
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void applyAdditionsToToken(Token token) {
|
||||
if (becomesArtifact) {
|
||||
token.addCardType(CardType.ARTIFACT);
|
||||
}
|
||||
|
|
@ -281,19 +303,6 @@ public class CreateTokenCopyTargetEffect extends OneShotEffect {
|
|||
token.removeAbility(ability);
|
||||
}
|
||||
}
|
||||
|
||||
token.putOntoBattlefield(number, game, source, playerId == null ? source.getControllerId() : playerId, tapped, attacking, attackedPlayer, attachedTo);
|
||||
for (UUID tokenId : token.getLastAddedTokenIds()) { // by cards like Doubling Season multiple tokens can be added to the battlefield
|
||||
Permanent tokenPermanent = game.getPermanent(tokenId);
|
||||
if (tokenPermanent != null) {
|
||||
addedTokenPermanents.add(tokenPermanent);
|
||||
// TODO: Workaround to add counters to all created tokens, necessary for correct interactions with cards like Chatterfang, Squirrel General and Ochre Jelly / Printlifter Ooze. See #10786
|
||||
if (counter != null && numberOfCounters > 0) {
|
||||
tokenPermanent.addCounters(counter.createInstance(numberOfCounters), source.getControllerId(), source, game);
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ package mage.abilities.effects.common;
|
|||
import mage.abilities.Ability;
|
||||
import mage.abilities.effects.OneShotEffect;
|
||||
import mage.cards.Card;
|
||||
import mage.cards.ModalDoubleFacedCard;
|
||||
import mage.constants.Outcome;
|
||||
import mage.constants.Zone;
|
||||
import mage.game.Game;
|
||||
|
|
@ -37,10 +36,6 @@ public class ReturnToHandAttachedEffect extends OneShotEffect {
|
|||
return false;
|
||||
}
|
||||
Card card = permanent.getMainCard();
|
||||
// TODO: Once MDFC ZCC increments are fixed properly, can remove this special case. For now must allow so effect works.
|
||||
if (permanent.getZoneChangeCounter(game) + 1 != card.getZoneChangeCounter(game) && !(card instanceof ModalDoubleFacedCard)) {
|
||||
return false;
|
||||
}
|
||||
return player.moveCards(card, Zone.HAND, source, game);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,12 @@
|
|||
package mage.abilities.effects.common;
|
||||
|
||||
import mage.MageObject;
|
||||
import mage.Mana;
|
||||
import mage.abilities.Abilities;
|
||||
import mage.abilities.AbilitiesImpl;
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.SpellAbility;
|
||||
import mage.abilities.common.RoomUnlockAbility;
|
||||
import mage.abilities.costs.mana.ManaCost;
|
||||
import mage.abilities.costs.mana.ManaCosts;
|
||||
import mage.abilities.costs.mana.ManaCostsImpl;
|
||||
import mage.abilities.effects.ContinuousEffectImpl;
|
||||
|
|
@ -15,12 +19,13 @@ import mage.constants.SubLayer;
|
|||
import mage.game.Game;
|
||||
import mage.game.permanent.Permanent;
|
||||
import mage.game.permanent.PermanentCard;
|
||||
import mage.util.CardUtil;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* @author oscscull
|
||||
* Continuous effect that sets the name and mana value of a Room permanent based
|
||||
* on its unlocked halves.
|
||||
*
|
||||
* Functions as a characteristic-defining ability.
|
||||
* 709.5. Some split cards are permanent cards with a single shared type line.
|
||||
* A shared type line on such an object represents two static abilities that
|
||||
|
|
@ -33,11 +38,13 @@ import mage.game.permanent.PermanentCard;
|
|||
* object's right half."
|
||||
* These abilities, as well as which half of that permanent a characteristic is
|
||||
* in, are part of that object's copiable values.
|
||||
* @author oscscull
|
||||
*/
|
||||
public class RoomCharacteristicsEffect extends ContinuousEffectImpl {
|
||||
|
||||
|
||||
public RoomCharacteristicsEffect() {
|
||||
super(Duration.WhileOnBattlefield, Layer.PTChangingEffects_7, SubLayer.CharacteristicDefining_7a,
|
||||
super(Duration.WhileOnBattlefield, Layer.TextChangingEffects_3, SubLayer.NA,
|
||||
Outcome.Neutral);
|
||||
staticText = "";
|
||||
}
|
||||
|
|
@ -59,6 +66,107 @@ public class RoomCharacteristicsEffect extends ContinuousEffectImpl {
|
|||
return false;
|
||||
}
|
||||
|
||||
return removeCharacteristics(game, permanent);
|
||||
}
|
||||
|
||||
public boolean removeCharacteristics(Game game, Permanent permanent) {
|
||||
Card roomCardBlueprint = getCard(permanent);
|
||||
|
||||
if (!(roomCardBlueprint instanceof SplitCard)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
SplitCard roomCard = (SplitCard) roomCardBlueprint;
|
||||
|
||||
// Remove the name based on unlocked halves
|
||||
String newName = permanent.getName();
|
||||
|
||||
boolean isLeftUnlocked = permanent.isLeftDoorUnlocked();
|
||||
if (!isLeftUnlocked && roomCard.getLeftHalfCard() != null) {
|
||||
newName = newName.replace(roomCard.getLeftHalfCard().getName() + " // ", "");
|
||||
}
|
||||
|
||||
boolean isRightUnlocked = permanent.isRightDoorUnlocked();
|
||||
if (!isRightUnlocked && roomCard.getRightHalfCard() != null) {
|
||||
newName = newName
|
||||
.replace(" // " + roomCard.getRightHalfCard().getName(), "")
|
||||
.replace(roomCard.getRightHalfCard().getName(), "");
|
||||
}
|
||||
|
||||
permanent.setName(newName);
|
||||
|
||||
// Set the mana value based on unlocked halves
|
||||
// Create a new Mana object to accumulate the costs
|
||||
SpellAbility roomCardSpellAbility = roomCard.getSpellAbility().copy();
|
||||
// Remove the mana from the left half's cost to our total Mana object
|
||||
if (!isLeftUnlocked) {
|
||||
ManaCosts<ManaCost> leftHalfManaCost = null;
|
||||
if (roomCard.getLeftHalfCard() != null && roomCard.getLeftHalfCard().getSpellAbility() != null) {
|
||||
leftHalfManaCost = roomCard.getLeftHalfCard().getSpellAbility().getManaCosts();
|
||||
}
|
||||
if (leftHalfManaCost != null) {
|
||||
CardUtil.adjustCost(roomCardSpellAbility, leftHalfManaCost, true);
|
||||
}
|
||||
}
|
||||
|
||||
// Remove the mana from the right half's cost to our total Mana object
|
||||
if (!isRightUnlocked) {
|
||||
ManaCosts<ManaCost> rightHalfManaCost = null;
|
||||
if (roomCard.getRightHalfCard() != null && roomCard.getRightHalfCard().getSpellAbility() != null) {
|
||||
rightHalfManaCost = roomCard.getRightHalfCard().getSpellAbility().getManaCosts();
|
||||
}
|
||||
if (rightHalfManaCost != null) {
|
||||
CardUtil.adjustCost(roomCardSpellAbility, rightHalfManaCost, true);
|
||||
}
|
||||
}
|
||||
|
||||
ManaCosts<ManaCost> roomCardManaCosts = roomCardSpellAbility.getManaCostsToPay();
|
||||
if (roomCardManaCosts.getText().equals("{0}")) {
|
||||
roomCardManaCosts = new ManaCostsImpl<>();
|
||||
}
|
||||
permanent.setManaCost(roomCardManaCosts);
|
||||
|
||||
|
||||
// Remove abilities from locked halves and add unlock abilities
|
||||
Abilities<Ability> removedLeftAbilities = new AbilitiesImpl<>();
|
||||
Abilities<Ability> removedRightAbilities = new AbilitiesImpl<>();
|
||||
Card abilitySource = permanent;
|
||||
if (permanent.isCopy()) {
|
||||
abilitySource = (Card) permanent.getCopyFrom();
|
||||
}
|
||||
for (Ability ability : abilitySource.getAbilities(game)) {
|
||||
if (!isLeftUnlocked) {
|
||||
if (roomCard.getLeftHalfCard() != null && roomCard.getLeftHalfCard().getAbilities().contains(ability)) {
|
||||
if (!removedLeftAbilities.contains(ability)) {
|
||||
removedLeftAbilities.add(ability);
|
||||
}
|
||||
permanent.removeAbility(ability, null, game);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (!isRightUnlocked) {
|
||||
if (roomCard.getRightHalfCard() != null && roomCard.getRightHalfCard().getAbilities().contains(ability)) {
|
||||
if (!removedRightAbilities.contains(ability)) {
|
||||
removedRightAbilities.add(ability);
|
||||
}
|
||||
permanent.removeAbility(ability, null, game);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Add the Special Action to unlock doors.
|
||||
// These will ONLY be active if the corresponding half is LOCKED!
|
||||
if (!removedLeftAbilities.isEmpty()) {
|
||||
RoomUnlockAbility leftUnlockAbility = new RoomUnlockAbility(roomCard.getLeftHalfCard().getManaCost(), true);
|
||||
permanent.addAbility(leftUnlockAbility, roomCard.getLeftHalfCard().getId(), game);
|
||||
}
|
||||
if (!removedRightAbilities.isEmpty()) {
|
||||
RoomUnlockAbility rightUnlockAbility = new RoomUnlockAbility(roomCard.getRightHalfCard().getManaCost(), false);
|
||||
permanent.addAbility(rightUnlockAbility, roomCard.getRightHalfCard().getId(), game);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static Card getCard(Permanent permanent) {
|
||||
Card roomCardBlueprint;
|
||||
|
||||
// Handle copies
|
||||
|
|
@ -74,69 +182,34 @@ public class RoomCharacteristicsEffect extends ContinuousEffectImpl {
|
|||
} else {
|
||||
roomCardBlueprint = permanent.getMainCard();
|
||||
}
|
||||
return roomCardBlueprint;
|
||||
}
|
||||
|
||||
if (!(roomCardBlueprint instanceof SplitCard)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
SplitCard roomCard = (SplitCard) roomCardBlueprint;
|
||||
|
||||
// Set the name based on unlocked halves
|
||||
String newName = "";
|
||||
|
||||
boolean isLeftUnlocked = permanent.isLeftDoorUnlocked();
|
||||
if (isLeftUnlocked && roomCard.getLeftHalfCard() != null) {
|
||||
newName += roomCard.getLeftHalfCard().getName();
|
||||
}
|
||||
|
||||
boolean isRightUnlocked = permanent.isRightDoorUnlocked();
|
||||
if (isRightUnlocked && roomCard.getRightHalfCard() != null) {
|
||||
if (!newName.isEmpty()) {
|
||||
newName += " // "; // Split card name separator
|
||||
}
|
||||
newName += roomCard.getRightHalfCard().getName();
|
||||
}
|
||||
|
||||
permanent.setName(newName);
|
||||
|
||||
// Set the mana value based on unlocked halves
|
||||
// Create a new Mana object to accumulate the costs
|
||||
Mana totalManaCost = new Mana();
|
||||
|
||||
// Add the mana from the left half's cost to our total Mana object
|
||||
if (isLeftUnlocked) {
|
||||
ManaCosts leftHalfManaCost = null;
|
||||
if (roomCard.getLeftHalfCard() != null && roomCard.getLeftHalfCard().getSpellAbility() != null) {
|
||||
leftHalfManaCost = roomCard.getLeftHalfCard().getSpellAbility().getManaCosts();
|
||||
}
|
||||
if (leftHalfManaCost != null) {
|
||||
totalManaCost.add(leftHalfManaCost.getMana());
|
||||
public void restoreUnlockedStats(Game game, Permanent permanent) {
|
||||
// remove unlock abilities
|
||||
for (Ability ability : permanent.getAbilities(game)) {
|
||||
if (ability instanceof RoomUnlockAbility) {
|
||||
if (((RoomUnlockAbility) ability).isLeftHalf() && permanent.isLeftDoorUnlocked()) {
|
||||
permanent.removeAbility(ability, null, game);
|
||||
} else if (!((RoomUnlockAbility) ability).isLeftHalf() && permanent.isRightDoorUnlocked()) {
|
||||
permanent.removeAbility(ability, null, game);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add the mana from the right half's cost to our total Mana object
|
||||
if (isRightUnlocked) {
|
||||
ManaCosts rightHalfManaCost = null;
|
||||
if (roomCard.getRightHalfCard() != null && roomCard.getRightHalfCard().getSpellAbility() != null) {
|
||||
rightHalfManaCost = roomCard.getRightHalfCard().getSpellAbility().getManaCosts();
|
||||
}
|
||||
if (rightHalfManaCost != null) {
|
||||
totalManaCost.add(rightHalfManaCost.getMana());
|
||||
// restore removed abilities
|
||||
// copies need abilities to be added back to game state for triggers
|
||||
SplitCard roomCard = (SplitCard) getCard(permanent);
|
||||
UUID sourceId = permanent.isCopy() ? permanent.getId() : null;
|
||||
Game gameParam = permanent.isCopy() ? game : null;
|
||||
if (permanent.isLeftDoorUnlocked()) {
|
||||
for (Ability ability : roomCard.getLeftHalfCard().getAbilities()) {
|
||||
permanent.addAbility(ability, sourceId, gameParam, true);
|
||||
}
|
||||
}
|
||||
|
||||
String newManaCostString = totalManaCost.toString();
|
||||
ManaCostsImpl newManaCosts;
|
||||
|
||||
// If both halves are locked or total 0, it's 0mv.
|
||||
if (newManaCostString.isEmpty() || totalManaCost.count() == 0) {
|
||||
newManaCosts = new ManaCostsImpl<>("");
|
||||
} else {
|
||||
newManaCosts = new ManaCostsImpl<>(newManaCostString);
|
||||
if (permanent.isRightDoorUnlocked()) {
|
||||
for (Ability ability : roomCard.getRightHalfCard().getAbilities()) {
|
||||
permanent.addAbility(ability, sourceId, gameParam, true);
|
||||
}
|
||||
}
|
||||
|
||||
permanent.setManaCost(newManaCosts);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
@ -16,7 +16,7 @@ import mage.abilities.effects.common.InfoEffect;
|
|||
import mage.abilities.keyword.WardAbility;
|
||||
import mage.cards.Card;
|
||||
import mage.cards.CardImpl;
|
||||
import mage.cards.ModalDoubleFacedCard;
|
||||
import mage.cards.DoubleFacedCard;
|
||||
import mage.cards.repository.TokenInfo;
|
||||
import mage.cards.repository.TokenRepository;
|
||||
import mage.constants.*;
|
||||
|
|
@ -375,9 +375,9 @@ public class BecomesFaceDownCreatureEffect extends ContinuousEffectImpl {
|
|||
// it can't transform. If the front face of the card is a creature card, you can turn it face up by paying
|
||||
// its mana cost. If you do, its front face will be up.
|
||||
|
||||
if (card instanceof ModalDoubleFacedCard) {
|
||||
if (card instanceof DoubleFacedCard) {
|
||||
// only MDFC uses independent card sides on 2024
|
||||
return ((ModalDoubleFacedCard) card).getLeftHalfCard();
|
||||
return ((DoubleFacedCard) card).getLeftHalfCard();
|
||||
} else {
|
||||
return card;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import mage.abilities.costs.common.ExileSourceCost;
|
|||
import mage.abilities.costs.mana.ManaCostsImpl;
|
||||
import mage.abilities.effects.OneShotEffect;
|
||||
import mage.cards.Card;
|
||||
import mage.cards.TransformingDoubleFacedCardHalf;
|
||||
import mage.constants.*;
|
||||
import mage.filter.FilterCard;
|
||||
import mage.filter.FilterPermanent;
|
||||
|
|
|
|||
|
|
@ -300,6 +300,7 @@ class ForetellAddCostEffect extends ContinuousEffectImpl {
|
|||
if (game.getState().getZone(mainCardId) == Zone.EXILED) {
|
||||
String foretellCost = (String) game.getState().getValue(mainCardId.toString() + "Foretell Cost");
|
||||
String foretellSplitCost = (String) game.getState().getValue(mainCardId.toString() + "Foretell Split Cost");
|
||||
// TODO: clean this up
|
||||
if (card instanceof SplitCard) {
|
||||
if (foretellCost != null) {
|
||||
SplitCardHalf leftHalfCard = ((SplitCard) card).getLeftHalfCard();
|
||||
|
|
@ -363,6 +364,14 @@ class ForetellAddCostEffect extends ContinuousEffectImpl {
|
|||
ability.setAbilityName(spellCard.getName());
|
||||
game.getState().addOtherAbility(spellCard, ability);
|
||||
}
|
||||
} else if (card instanceof TransformingDoubleFacedCard && foretellCost != null) {
|
||||
Card frontCard = ((TransformingDoubleFacedCard) card).getLeftHalfCard();
|
||||
ForetellCostAbility ability = new ForetellCostAbility(foretellCost);
|
||||
ability.setSourceId(frontCard.getId());
|
||||
ability.setControllerId(source.getControllerId());
|
||||
ability.setSpellAbilityType(frontCard.getSpellAbility().getSpellAbilityType());
|
||||
ability.setAbilityName(frontCard.getName());
|
||||
game.getState().addOtherAbility(frontCard, ability);
|
||||
} else if (foretellCost != null) {
|
||||
ForetellCostAbility ability = new ForetellCostAbility(foretellCost);
|
||||
ability.setSourceId(card.getId());
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ import mage.abilities.effects.common.counter.RemoveCounterSourceEffect;
|
|||
import mage.abilities.triggers.BeginningOfUpkeepTriggeredAbility;
|
||||
import mage.cards.Card;
|
||||
import mage.cards.CardsImpl;
|
||||
import mage.cards.ModalDoubleFacedCard;
|
||||
import mage.cards.DoubleFacedCard;
|
||||
import mage.constants.*;
|
||||
import mage.counters.CounterType;
|
||||
import mage.filter.StaticFilters;
|
||||
|
|
@ -177,10 +177,10 @@ public class SuspendAbility extends SpecialAction {
|
|||
* or added by Jhoira of the Ghitu
|
||||
*/
|
||||
public static void addSuspendTemporaryToCard(Card card, Ability source, Game game) {
|
||||
if (card instanceof ModalDoubleFacedCard) {
|
||||
if (card instanceof DoubleFacedCard) {
|
||||
// Need to ensure the suspend ability gets put on the left side card
|
||||
// since counters get added to this card.
|
||||
card = ((ModalDoubleFacedCard) card).getLeftHalfCard();
|
||||
card = ((DoubleFacedCard) card).getLeftHalfCard();
|
||||
}
|
||||
SuspendAbility ability = new SuspendAbility(0, null, card, false);
|
||||
ability.setSourceId(card.getId());
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package mage.cards;
|
||||
|
||||
import mage.MageInt;
|
||||
import mage.MageObject;
|
||||
import mage.Mana;
|
||||
import mage.abilities.Abilities;
|
||||
|
|
@ -72,6 +73,7 @@ public interface Card extends MageObject, Ownerable {
|
|||
|
||||
SpellAbility getSecondFaceSpellAbility();
|
||||
|
||||
//TODO: remove after tdfc rework
|
||||
boolean isNightCard();
|
||||
|
||||
default boolean meldsWith(Card card) {
|
||||
|
|
@ -250,6 +252,10 @@ public interface Card extends MageObject, Ownerable {
|
|||
|
||||
List<UUID> getAttachments();
|
||||
|
||||
void setPT(int power, int toughness);
|
||||
|
||||
void setPT(MageInt power, MageInt toughness);
|
||||
|
||||
/**
|
||||
* @param attachment can be any object: card, permanent, token
|
||||
* @param source can be null for default checks like state base
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package mage.cards;
|
||||
|
||||
import mage.MageInt;
|
||||
import mage.MageObject;
|
||||
import mage.MageObjectImpl;
|
||||
import mage.Mana;
|
||||
|
|
@ -126,6 +127,11 @@ public abstract class CardImpl extends MageObjectImpl implements Card {
|
|||
nightCard = card.nightCard;
|
||||
secondSideCardClazz = card.secondSideCardClazz;
|
||||
secondSideCard = null; // will be set on first getSecondCardFace call if card has one
|
||||
// TODO: temporary until cards tdfc cards are converted
|
||||
// can do normal copy after
|
||||
if (card.secondSideCard instanceof DoubleFacedCardHalf) {
|
||||
secondSideCard = card.secondSideCard.copy();
|
||||
}
|
||||
if (card.secondSideCard instanceof MockableCard) {
|
||||
// workaround to support gui's mock cards
|
||||
secondSideCard = card.secondSideCard.copy();
|
||||
|
|
@ -393,6 +399,17 @@ public abstract class CardImpl extends MageObjectImpl implements Card {
|
|||
this.abilities.setControllerId(ownerId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPT(int power, int toughness) {
|
||||
this.setPT(new MageInt(power), new MageInt(toughness));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPT(MageInt power, MageInt toughness) {
|
||||
this.power = power;
|
||||
this.toughness = toughness;
|
||||
}
|
||||
|
||||
@Override
|
||||
public UUID getControllerOrOwnerId() {
|
||||
return getOwnerId();
|
||||
|
|
@ -517,13 +534,13 @@ public abstract class CardImpl extends MageObjectImpl implements Card {
|
|||
}
|
||||
}
|
||||
|
||||
// handle half of Modal Double Faces Cards on stack
|
||||
if (stackObject == null && (this instanceof ModalDoubleFacedCard)) {
|
||||
stackObject = game.getStack().getSpell(((ModalDoubleFacedCard) this).getLeftHalfCard().getId(),
|
||||
// handle half of Double Faces Cards on stack
|
||||
if (stackObject == null && (this instanceof DoubleFacedCard)) {
|
||||
stackObject = game.getStack().getSpell(((DoubleFacedCard) this).getLeftHalfCard().getId(),
|
||||
false);
|
||||
if (stackObject == null) {
|
||||
stackObject = game.getStack()
|
||||
.getSpell(((ModalDoubleFacedCard) this).getRightHalfCard().getId(), false);
|
||||
.getSpell(((DoubleFacedCard) this).getRightHalfCard().getId(), false);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -650,7 +667,7 @@ public abstract class CardImpl extends MageObjectImpl implements Card {
|
|||
// If a spell or ability instructs a player to transform a permanent that
|
||||
// isn’t represented by a transforming token or a transforming double-faced
|
||||
// card, nothing happens.
|
||||
return this.secondSideCardClazz != null || this.nightCard;
|
||||
return this.secondSideCardClazz != null || this.nightCard || this.secondSideCard != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -947,7 +964,7 @@ public abstract class CardImpl extends MageObjectImpl implements Card {
|
|||
}
|
||||
}
|
||||
}
|
||||
if (controller != null && spellAbility != null && !spellAbility.getTargets().isEmpty()){
|
||||
if (controller != null && spellAbility != null && !spellAbility.getTargets().isEmpty()) {
|
||||
// Line of code below functionally gets the target of the aura's Enchant ability, then compares to this permanent. Enchant improperly implemented in XMage, see #9583
|
||||
// Note: stillLegalTarget used exclusively to account for Dream Leash. Can be made canTarget in the event that that card is rewritten (and "stillLegalTarget" removed from TargetImpl).
|
||||
canAttach &= spellAbility.getTargets().get(0).copy().withNotTarget(true).stillLegalTarget(controller, this.getId(), source, game);
|
||||
|
|
|
|||
413
Mage/src/main/java/mage/cards/DoubleFacedCard.java
Normal file
413
Mage/src/main/java/mage/cards/DoubleFacedCard.java
Normal file
|
|
@ -0,0 +1,413 @@
|
|||
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.Counter;
|
||||
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.List;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* @author JayDi85 - originally from ModalDoubleFaceCard
|
||||
*/
|
||||
public abstract class DoubleFacedCard extends CardImpl implements CardWithHalves {
|
||||
|
||||
protected DoubleFacedCardHalf leftHalfCard; // main card in all zone
|
||||
protected DoubleFacedCardHalf rightHalfCard; // second side card, can be only in stack and battlefield zones
|
||||
|
||||
protected DoubleFacedCard(UUID ownerId, CardSetInfo setInfo, CardType[] cardTypes, String costs, SpellAbilityType spellAbilityType) {
|
||||
super(ownerId, setInfo, cardTypes, costs, spellAbilityType);
|
||||
}
|
||||
|
||||
public DoubleFacedCard(DoubleFacedCard card) {
|
||||
super(card);
|
||||
// make sure all parts created and parent ref added
|
||||
this.leftHalfCard = (DoubleFacedCardHalf) card.getLeftHalfCard().copy();
|
||||
leftHalfCard.setParentCard(this);
|
||||
this.rightHalfCard = (DoubleFacedCardHalf) card.getRightHalfCard().copy();
|
||||
rightHalfCard.setParentCard(this);
|
||||
}
|
||||
|
||||
public DoubleFacedCardHalf getLeftHalfCard() {
|
||||
return leftHalfCard;
|
||||
}
|
||||
|
||||
public DoubleFacedCardHalf getRightHalfCard() {
|
||||
return leftHalfCard;
|
||||
}
|
||||
|
||||
public void setParts(DoubleFacedCardHalf leftHalfCard, DoubleFacedCardHalf rightHalfCard) {
|
||||
// for card copy only - set new parts
|
||||
this.leftHalfCard = leftHalfCard;
|
||||
leftHalfCard.setParentCard(this);
|
||||
this.rightHalfCard = rightHalfCard;
|
||||
rightHalfCard.setParentCard(this);
|
||||
}
|
||||
|
||||
@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);
|
||||
}
|
||||
|
||||
private void setSideZones(Zone mainZone, Game game) {
|
||||
switch (mainZone) {
|
||||
case BATTLEFIELD:
|
||||
case STACK:
|
||||
throw new IllegalArgumentException("Wrong code usage: you must put to battlefield/stack only real side card (half), not main");
|
||||
default:
|
||||
game.setZone(leftHalfCard.getId(), mainZone);
|
||||
game.setZone(rightHalfCard.getId(), mainZone);
|
||||
break;
|
||||
}
|
||||
checkGoodZones(game, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean moveToZone(Zone toZone, Ability source, Game game, boolean flag, List<UUID> appliedEffects) {
|
||||
if (super.moveToZone(toZone, source, game, flag, appliedEffects)) {
|
||||
Zone currentZone = game.getState().getZone(getId());
|
||||
setSideZones(currentZone, game);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setZone(Zone zone, Game game) {
|
||||
super.setZone(zone, game);
|
||||
setSideZones(zone, game);
|
||||
}
|
||||
|
||||
@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());
|
||||
setSideZones(currentZone, game);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Runtime check for good zones and other MDF data
|
||||
*/
|
||||
public static void checkGoodZones(Game game, DoubleFacedCard card) {
|
||||
Card leftPart = card.getLeftHalfCard();
|
||||
Card rightPart = card.getRightHalfCard();
|
||||
|
||||
Zone zoneMain = game.getState().getZone(card.getId());
|
||||
Zone zoneLeft = game.getState().getZone(leftPart.getId());
|
||||
Zone zoneRight = game.getState().getZone(rightPart.getId());
|
||||
|
||||
// runtime check:
|
||||
// * in battlefield and stack - card + one of the sides (another side in outside zone)
|
||||
// * in other zones - card + both sides (need both sides due cost reductions, spell and other access before put to stack)
|
||||
//
|
||||
// 712.8a While a double-faced card is outside the game or in a zone other than the battlefield or stack,
|
||||
// it has only the characteristics of its front face.
|
||||
//
|
||||
// 712.8f While a modal double-faced spell is on the stack or a modal double-faced permanent is on the battlefield,
|
||||
// it has only the characteristics of the face that’s up.
|
||||
Zone needZoneLeft;
|
||||
Zone needZoneRight;
|
||||
switch (zoneMain) {
|
||||
case BATTLEFIELD:
|
||||
case STACK:
|
||||
if (zoneMain == zoneLeft) {
|
||||
needZoneLeft = zoneMain;
|
||||
needZoneRight = Zone.OUTSIDE;
|
||||
} else if (zoneMain == zoneRight) {
|
||||
needZoneLeft = Zone.OUTSIDE;
|
||||
needZoneRight = zoneMain;
|
||||
} else {
|
||||
// impossible
|
||||
needZoneLeft = zoneMain;
|
||||
needZoneRight = Zone.OUTSIDE;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
needZoneLeft = zoneMain;
|
||||
needZoneRight = zoneMain;
|
||||
break;
|
||||
}
|
||||
|
||||
if (zoneLeft != needZoneLeft || zoneRight != needZoneRight) {
|
||||
throw new IllegalStateException("Wrong code usage: MDF card uses wrong zones - " + card
|
||||
+ "\r\n" + String.format("* main zone: %s", zoneMain)
|
||||
+ "\r\n" + String.format("* left side: need %s, actual %s", needZoneLeft, zoneLeft)
|
||||
+ "\r\n" + String.format("* right side: need %s, actual %s", needZoneRight, zoneRight));
|
||||
}
|
||||
}
|
||||
|
||||
@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);
|
||||
game.getState().updateZoneChangeCounter(leftHalfCard.getId());
|
||||
game.getState().updateZoneChangeCounter(rightHalfCard.getId());
|
||||
}
|
||||
|
||||
@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 addCounters(Counter counter, UUID playerAddingCounters, Ability source, Game game, List<UUID> appliedEffects, boolean isEffect, int maxCounters) {
|
||||
return leftHalfCard.addCounters(counter, playerAddingCounters, source, game, appliedEffects, isEffect, maxCounters);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeCounters(String counterName, int amount, Ability source, Game game) {
|
||||
leftHalfCard.removeCounters(counterName, amount, source, game);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean cast(Game game, Zone fromZone, SpellAbility ability, UUID controllerId) {
|
||||
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 List<SuperType> getSuperType(Game game) {
|
||||
// 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(game) : supertype;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<CardType> getCardType(Game game) {
|
||||
// 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(game) : 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(true, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Abilities<Ability> getInitAbilities() {
|
||||
// must init only parent related abilities, spell card must be init separately
|
||||
return getInnerAbilities(false, 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, true, true);
|
||||
}
|
||||
|
||||
private boolean isIgnoreDefaultAbility(Ability ability) {
|
||||
// ignore default play/spell ability from main card (only halves are actual)
|
||||
// default abilities added on card creation from card type and can't be skipped
|
||||
|
||||
// skip cast spell
|
||||
if (ability instanceof SpellAbility) {
|
||||
SpellAbilityType type = ((SpellAbility) ability).getSpellAbilityType();
|
||||
return type == SpellAbilityType.MODAL || type == SpellAbilityType.TRANSFORMED;
|
||||
}
|
||||
|
||||
// skip play land
|
||||
return ability instanceof PlayLandAbility;
|
||||
}
|
||||
|
||||
private boolean isIgnoreTransformSpellAbility(Ability ability) {
|
||||
return ability instanceof SpellAbility && ((SpellAbility) ability).getSpellAbilityType() == SpellAbilityType.TRANSFORMED_RIGHT;
|
||||
}
|
||||
|
||||
private Abilities<Ability> getInnerAbilities(Game game, boolean showLeftSide, boolean showRightSide) {
|
||||
Abilities<Ability> allAbilites = new AbilitiesImpl<>();
|
||||
|
||||
for (Ability ability : super.getAbilities(game)) {
|
||||
if (isIgnoreDefaultAbility(ability)) {
|
||||
continue;
|
||||
}
|
||||
allAbilites.add(ability);
|
||||
}
|
||||
|
||||
if (showLeftSide) {
|
||||
allAbilites.addAll(leftHalfCard.getAbilities(game));
|
||||
}
|
||||
if (showRightSide) {
|
||||
for (Ability ability: rightHalfCard.getAbilities(game)) {
|
||||
if (isIgnoreTransformSpellAbility(ability)) {
|
||||
continue;
|
||||
}
|
||||
allAbilites.add(ability);
|
||||
}
|
||||
}
|
||||
|
||||
return allAbilites;
|
||||
}
|
||||
|
||||
private Abilities<Ability> getInnerAbilities(boolean showLeftSide, boolean showRightSide) {
|
||||
Abilities<Ability> allAbilites = new AbilitiesImpl<>();
|
||||
|
||||
for (Ability ability : super.getAbilities()) {
|
||||
if (isIgnoreDefaultAbility(ability)) {
|
||||
continue;
|
||||
}
|
||||
allAbilites.add(ability);
|
||||
}
|
||||
|
||||
if (showLeftSide) {
|
||||
allAbilites.addAll(leftHalfCard.getAbilities());
|
||||
}
|
||||
|
||||
if (showRightSide) {
|
||||
for (Ability ability: rightHalfCard.getAbilities()) {
|
||||
if (isIgnoreTransformSpellAbility(ability)) {
|
||||
continue;
|
||||
}
|
||||
allAbilites.add(ability);
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
this.getInnerAbilities(true, false),
|
||||
this.getInnerAbilities(true, true)
|
||||
);
|
||||
}
|
||||
|
||||
@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,
|
||||
this.getInnerAbilities(game, true, false),
|
||||
this.getInnerAbilities(game, true, true)
|
||||
);
|
||||
}
|
||||
|
||||
@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 getManaValue() {
|
||||
// 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.getManaValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public MageInt getPower() {
|
||||
return leftHalfCard.getPower();
|
||||
}
|
||||
|
||||
@Override
|
||||
public MageInt getToughness() {
|
||||
return leftHalfCard.getToughness();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,25 +1,25 @@
|
|||
package mage.cards;
|
||||
|
||||
import mage.MageInt;
|
||||
import mage.abilities.Ability;
|
||||
import mage.constants.*;
|
||||
import mage.game.Game;
|
||||
import mage.game.events.ZoneChangeEvent;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* @author JayDi85
|
||||
* @author JayDi85 - originally from ModalDoubleFaceCardHalf
|
||||
*/
|
||||
public class ModalDoubleFacedCardHalfImpl extends CardImpl implements ModalDoubleFacedCardHalf {
|
||||
public abstract class DoubleFacedCardHalf extends CardImpl implements SubCard<DoubleFacedCard> {
|
||||
|
||||
ModalDoubleFacedCard parentCard;
|
||||
protected DoubleFacedCard parentCard;
|
||||
|
||||
public ModalDoubleFacedCardHalfImpl(
|
||||
public DoubleFacedCardHalf(
|
||||
UUID ownerId, CardSetInfo setInfo,
|
||||
SuperType[] cardSuperTypes, CardType[] cardTypes, SubType[] cardSubTypes,
|
||||
String costs, ModalDoubleFacedCard parentCard, SpellAbilityType spellAbilityType
|
||||
String costs, DoubleFacedCard parentCard, SpellAbilityType spellAbilityType
|
||||
) {
|
||||
super(ownerId, setInfo, cardTypes, costs, spellAbilityType);
|
||||
this.supertype.addAll(Arrays.asList(cardSuperTypes));
|
||||
|
|
@ -27,7 +27,7 @@ public class ModalDoubleFacedCardHalfImpl extends CardImpl implements ModalDoubl
|
|||
this.parentCard = parentCard;
|
||||
}
|
||||
|
||||
protected ModalDoubleFacedCardHalfImpl(final ModalDoubleFacedCardHalfImpl card) {
|
||||
protected DoubleFacedCardHalf(final DoubleFacedCardHalf card) {
|
||||
super(card);
|
||||
this.parentCard = card.parentCard;
|
||||
}
|
||||
|
|
@ -49,6 +49,11 @@ public class ModalDoubleFacedCardHalfImpl extends CardImpl implements ModalDoubl
|
|||
return parentCard.getCardNumber();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTransformable() {
|
||||
return getOtherSide().isPermanent();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean moveToZone(Zone toZone, Ability source, Game game, boolean flag, List<UUID> appliedEffects) {
|
||||
return parentCard.moveToZone(toZone, source, game, flag, appliedEffects);
|
||||
|
|
@ -65,25 +70,23 @@ public class ModalDoubleFacedCardHalfImpl extends CardImpl implements ModalDoubl
|
|||
}
|
||||
|
||||
@Override
|
||||
public ModalDoubleFacedCard getMainCard() {
|
||||
public Card getMainCard() {
|
||||
return parentCard;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateZoneChangeCounter(Game game, ZoneChangeEvent event) {
|
||||
parentCard.updateZoneChangeCounter(game, event);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setZone(Zone zone, Game game) {
|
||||
// see ModalDoubleFacedCard.checkGoodZones for details
|
||||
// see DoubleFacedCard.checkGoodZones for details
|
||||
game.setZone(parentCard.getId(), zone);
|
||||
game.setZone(this.getId(), zone);
|
||||
|
||||
// find another side to sync
|
||||
ModalDoubleFacedCardHalf otherSide;
|
||||
if (!parentCard.getLeftHalfCard().getId().equals(this.getId())) {
|
||||
otherSide = parentCard.getLeftHalfCard();
|
||||
} else if (!parentCard.getRightHalfCard().getId().equals(this.getId())) {
|
||||
otherSide = parentCard.getRightHalfCard();
|
||||
} else {
|
||||
throw new IllegalStateException("Wrong code usage: MDF halves must use different ids");
|
||||
}
|
||||
Card otherSide = getOtherSide();
|
||||
|
||||
switch (zone) {
|
||||
case STACK:
|
||||
|
|
@ -96,33 +99,39 @@ public class ModalDoubleFacedCardHalfImpl extends CardImpl implements ModalDoubl
|
|||
break;
|
||||
}
|
||||
|
||||
ModalDoubleFacedCard.checkGoodZones(game, parentCard);
|
||||
DoubleFacedCard.checkGoodZones(game, parentCard);
|
||||
}
|
||||
|
||||
public Card getOtherSide() {
|
||||
Card otherSide;
|
||||
if (!parentCard.getLeftHalfCard().getId().equals(this.getId())) {
|
||||
otherSide = parentCard.getLeftHalfCard();
|
||||
} else if (!parentCard.getRightHalfCard().getId().equals(this.getId())) {
|
||||
otherSide = parentCard.getRightHalfCard();
|
||||
} else {
|
||||
throw new IllegalStateException("Wrong code usage: MDF halves must use different ids");
|
||||
}
|
||||
return otherSide;
|
||||
}
|
||||
|
||||
public boolean isBackSide() {
|
||||
if (parentCard.getLeftHalfCard().getId().equals(this.getId())) {
|
||||
return false;
|
||||
} else if (parentCard.getRightHalfCard().getId().equals(this.getId())) {
|
||||
return true;
|
||||
} else {
|
||||
throw new IllegalStateException("Wrong code usage: MDF halves must use different ids");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ModalDoubleFacedCardHalfImpl copy() {
|
||||
return new ModalDoubleFacedCardHalfImpl(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setParentCard(ModalDoubleFacedCard card) {
|
||||
public void setParentCard(DoubleFacedCard card) {
|
||||
this.parentCard = card;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ModalDoubleFacedCard getParentCard() {
|
||||
return this.parentCard;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPT(int power, int toughness) {
|
||||
this.setPT(new MageInt(power), new MageInt(toughness));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPT(MageInt power, MageInt toughness) {
|
||||
this.power = power;
|
||||
this.toughness = toughness;
|
||||
public DoubleFacedCard getParentCard() {
|
||||
return parentCard;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -1,30 +1,15 @@
|
|||
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.Counter;
|
||||
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.List;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* @author JayDi85
|
||||
*/
|
||||
public abstract class ModalDoubleFacedCard extends CardImpl implements CardWithHalves {
|
||||
|
||||
protected Card leftHalfCard; // main card in all zone
|
||||
protected Card rightHalfCard; // second side card, can be only in stack and battlefield zones
|
||||
public abstract class ModalDoubleFacedCard extends DoubleFacedCard {
|
||||
|
||||
public ModalDoubleFacedCard(
|
||||
UUID ownerId, CardSetInfo setInfo,
|
||||
|
|
@ -48,184 +33,21 @@ public abstract class ModalDoubleFacedCard extends CardImpl implements CardWithH
|
|||
) {
|
||||
super(ownerId, setInfo, typesLeft, costsLeft + costsRight, SpellAbilityType.MODAL);
|
||||
// main card name must be same as left side
|
||||
leftHalfCard = new ModalDoubleFacedCardHalfImpl(
|
||||
leftHalfCard = new ModalDoubleFacedCardHalf(
|
||||
this.getOwnerId(), setInfo.copy(),
|
||||
superTypesLeft, typesLeft, subTypesLeft, costsLeft,
|
||||
this, SpellAbilityType.MODAL_LEFT
|
||||
);
|
||||
rightHalfCard = new ModalDoubleFacedCardHalfImpl(
|
||||
rightHalfCard = new ModalDoubleFacedCardHalf(
|
||||
this.getOwnerId(), new CardSetInfo(secondSideName, setInfo),
|
||||
superTypesRight, typesRight, subTypesRight, costsRight,
|
||||
this, SpellAbilityType.MODAL_RIGHT
|
||||
);
|
||||
this.secondSideCard = rightHalfCard;
|
||||
}
|
||||
|
||||
public ModalDoubleFacedCard(ModalDoubleFacedCard card) {
|
||||
public ModalDoubleFacedCard(final ModalDoubleFacedCard card) {
|
||||
super(card);
|
||||
// make sure all parts created and parent ref added
|
||||
this.leftHalfCard = card.getLeftHalfCard().copy();
|
||||
((ModalDoubleFacedCardHalf) leftHalfCard).setParentCard(this);
|
||||
this.rightHalfCard = card.rightHalfCard.copy();
|
||||
((ModalDoubleFacedCardHalf) rightHalfCard).setParentCard(this);
|
||||
}
|
||||
|
||||
public ModalDoubleFacedCardHalf getLeftHalfCard() {
|
||||
return (ModalDoubleFacedCardHalf) leftHalfCard;
|
||||
}
|
||||
|
||||
public ModalDoubleFacedCardHalf getRightHalfCard() {
|
||||
return (ModalDoubleFacedCardHalf) rightHalfCard;
|
||||
}
|
||||
|
||||
public void setParts(ModalDoubleFacedCardHalf leftHalfCard, ModalDoubleFacedCardHalf rightHalfCard) {
|
||||
// for card copy only - set new parts
|
||||
this.leftHalfCard = leftHalfCard;
|
||||
leftHalfCard.setParentCard(this);
|
||||
this.rightHalfCard = rightHalfCard;
|
||||
rightHalfCard.setParentCard(this);
|
||||
}
|
||||
|
||||
@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); // TODO: must check copiedFrom and assign sides? (??? related to #8476 ???)
|
||||
rightHalfCard.setCopy(isCopy, copiedFrom);
|
||||
}
|
||||
|
||||
private void setSideZones(Zone mainZone, Game game) {
|
||||
switch (mainZone) {
|
||||
case BATTLEFIELD:
|
||||
case STACK:
|
||||
throw new IllegalArgumentException("Wrong code usage: you must put to battlefield/stack only real side card (half), not main");
|
||||
default:
|
||||
// must keep both sides in same zone cause xmage need access to cost reduction, spell
|
||||
// and other abilities before put it to stack (in playable calcs)
|
||||
game.setZone(leftHalfCard.getId(), mainZone);
|
||||
game.setZone(rightHalfCard.getId(), mainZone);
|
||||
break;
|
||||
}
|
||||
checkGoodZones(game, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean moveToZone(Zone toZone, Ability source, Game game, boolean flag, List<UUID> appliedEffects) {
|
||||
if (super.moveToZone(toZone, source, game, flag, appliedEffects)) {
|
||||
Zone currentZone = game.getState().getZone(getId());
|
||||
setSideZones(currentZone, game);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setZone(Zone zone, Game game) {
|
||||
super.setZone(zone, game);
|
||||
setSideZones(zone, game);
|
||||
}
|
||||
|
||||
@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());
|
||||
setSideZones(currentZone, game);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Runtime check for good zones and other MDF data
|
||||
*/
|
||||
public static void checkGoodZones(Game game, ModalDoubleFacedCard card) {
|
||||
Card leftPart = card.getLeftHalfCard();
|
||||
Card rightPart = card.getRightHalfCard();
|
||||
|
||||
Zone zoneMain = game.getState().getZone(card.getId());
|
||||
Zone zoneLeft = game.getState().getZone(leftPart.getId());
|
||||
Zone zoneRight = game.getState().getZone(rightPart.getId());
|
||||
|
||||
// runtime check:
|
||||
// * in battlefield and stack - card + one of the sides (another side in outside zone)
|
||||
// * in other zones - card + both sides (need both sides due cost reductions, spell and other access before put to stack)
|
||||
//
|
||||
// 712.8a While a double-faced card is outside the game or in a zone other than the battlefield or stack,
|
||||
// it has only the characteristics of its front face.
|
||||
//
|
||||
// 712.8f While a modal double-faced spell is on the stack or a modal double-faced permanent is on the battlefield,
|
||||
// it has only the characteristics of the face that’s up.
|
||||
Zone needZoneLeft;
|
||||
Zone needZoneRight;
|
||||
switch (zoneMain) {
|
||||
case BATTLEFIELD:
|
||||
case STACK:
|
||||
if (zoneMain == zoneLeft) {
|
||||
needZoneLeft = zoneMain;
|
||||
needZoneRight = Zone.OUTSIDE;
|
||||
} else if (zoneMain == zoneRight) {
|
||||
needZoneLeft = Zone.OUTSIDE;
|
||||
needZoneRight = zoneMain;
|
||||
} else {
|
||||
// impossible
|
||||
needZoneLeft = zoneMain;
|
||||
needZoneRight = Zone.OUTSIDE;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
needZoneLeft = zoneMain;
|
||||
needZoneRight = zoneMain;
|
||||
break;
|
||||
}
|
||||
|
||||
if (zoneLeft != needZoneLeft || zoneRight != needZoneRight) {
|
||||
throw new IllegalStateException("Wrong code usage: MDF card uses wrong zones - " + card
|
||||
+ "\r\n" + String.format("* main zone: %s", zoneMain)
|
||||
+ "\r\n" + String.format("* left side: need %s, actual %s", needZoneLeft, zoneLeft)
|
||||
+ "\r\n" + String.format("* right side: need %s, actual %s", needZoneRight, zoneRight));
|
||||
}
|
||||
}
|
||||
|
||||
@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 addCounters(Counter counter, UUID playerAddingCounters, Ability source, Game game, List<UUID> appliedEffects, boolean isEffect, int maxCounters) {
|
||||
return leftHalfCard.addCounters(counter, playerAddingCounters, source, game, appliedEffects, isEffect, maxCounters);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeCounters(String counterName, int amount, Ability source, Game game) {
|
||||
leftHalfCard.removeCounters(counterName, amount, source, game);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -236,201 +58,22 @@ public abstract class ModalDoubleFacedCard extends CardImpl implements CardWithH
|
|||
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 List<SuperType> getSuperType(Game game) {
|
||||
// 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(game) : supertype;
|
||||
public boolean isTransformable() {
|
||||
return this.getLeftHalfCard().isPermanent() && this.getRightHalfCard().isPermanent();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<CardType> getCardType(Game game) {
|
||||
// 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(game) : cardType;
|
||||
public ModalDoubleFacedCardHalf getLeftHalfCard() {
|
||||
return (ModalDoubleFacedCardHalf) leftHalfCard;
|
||||
}
|
||||
|
||||
@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(true, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Abilities<Ability> getInitAbilities() {
|
||||
// must init only parent related abilities, spell card must be init separately
|
||||
return getInnerAbilities(false, 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, true, true);
|
||||
}
|
||||
|
||||
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 showLeftSide, boolean showRightSide) {
|
||||
Abilities<Ability> allAbilites = new AbilitiesImpl<>();
|
||||
|
||||
for (Ability ability : super.getAbilities(game)) {
|
||||
if (isIgnoreDefaultAbility(ability)) {
|
||||
continue;
|
||||
}
|
||||
allAbilites.add(ability);
|
||||
}
|
||||
|
||||
if (showLeftSide) {
|
||||
allAbilites.addAll(leftHalfCard.getAbilities(game));
|
||||
}
|
||||
if (showRightSide) {
|
||||
allAbilites.addAll(rightHalfCard.getAbilities(game));
|
||||
}
|
||||
|
||||
return allAbilites;
|
||||
}
|
||||
|
||||
private Abilities<Ability> getInnerAbilities(boolean showLeftSide, boolean showRightSide) {
|
||||
Abilities<Ability> allAbilites = new AbilitiesImpl<>();
|
||||
|
||||
for (Ability ability : super.getAbilities()) {
|
||||
if (isIgnoreDefaultAbility(ability)) {
|
||||
continue;
|
||||
}
|
||||
allAbilites.add(ability);
|
||||
}
|
||||
|
||||
if (showLeftSide) {
|
||||
allAbilites.addAll(leftHalfCard.getAbilities());
|
||||
}
|
||||
|
||||
if (showRightSide) {
|
||||
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,
|
||||
this.getInnerAbilities(true, false),
|
||||
this.getInnerAbilities(true, true)
|
||||
);
|
||||
}
|
||||
|
||||
@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,
|
||||
this.getInnerAbilities(game, true, false),
|
||||
this.getInnerAbilities(game, true, true)
|
||||
);
|
||||
}
|
||||
|
||||
@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 getManaValue() {
|
||||
// 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.getManaValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public MageInt getPower() {
|
||||
return leftHalfCard.getPower();
|
||||
}
|
||||
|
||||
@Override
|
||||
public MageInt getToughness() {
|
||||
return leftHalfCard.getToughness();
|
||||
public ModalDoubleFacedCardHalf getRightHalfCard() {
|
||||
return (ModalDoubleFacedCardHalf) rightHalfCard;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,16 +1,34 @@
|
|||
package mage.cards;
|
||||
|
||||
import mage.MageInt;
|
||||
import mage.constants.CardType;
|
||||
import mage.constants.SpellAbilityType;
|
||||
import mage.constants.SubType;
|
||||
import mage.constants.SuperType;
|
||||
|
||||
/**
|
||||
* @author JayDi85
|
||||
*/
|
||||
public interface ModalDoubleFacedCardHalf extends SubCard<ModalDoubleFacedCard> {
|
||||
import java.util.UUID;
|
||||
|
||||
public class ModalDoubleFacedCardHalf extends DoubleFacedCardHalf {
|
||||
|
||||
public ModalDoubleFacedCardHalf(
|
||||
UUID ownerId, CardSetInfo setInfo,
|
||||
SuperType[] cardSuperTypes, CardType[] cardTypes, SubType[] cardSubTypes,
|
||||
String costs, ModalDoubleFacedCard parentCard, SpellAbilityType spellAbilityType
|
||||
) {
|
||||
super(ownerId, setInfo, cardSuperTypes, cardTypes, cardSubTypes, costs, parentCard, spellAbilityType);
|
||||
}
|
||||
|
||||
protected ModalDoubleFacedCardHalf(final ModalDoubleFacedCardHalf card) {
|
||||
super(card);
|
||||
this.parentCard = card.parentCard;
|
||||
}
|
||||
|
||||
@Override
|
||||
ModalDoubleFacedCardHalf copy();
|
||||
public ModalDoubleFacedCardHalf copy() {
|
||||
return new ModalDoubleFacedCardHalf(this);
|
||||
}
|
||||
|
||||
void setPT(int power, int toughness);
|
||||
|
||||
void setPT(MageInt power, MageInt toughness);
|
||||
@Override
|
||||
public ModalDoubleFacedCard getParentCard() {
|
||||
return (ModalDoubleFacedCard) parentCard;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,27 +1,20 @@
|
|||
package mage.cards;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import mage.ObjectColor;
|
||||
import mage.abilities.Abilities;
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.common.EntersBattlefieldAbility;
|
||||
import mage.abilities.common.RoomUnlockAbility;
|
||||
import mage.abilities.common.SimpleStaticAbility;
|
||||
import mage.abilities.common.UnlockThisDoorTriggeredAbility;
|
||||
import mage.abilities.condition.common.RoomHalfLockedCondition;
|
||||
import mage.abilities.costs.mana.ManaCosts;
|
||||
import mage.abilities.decorator.ConditionalContinuousEffect;
|
||||
import mage.abilities.common.RoomAbility;
|
||||
import mage.abilities.effects.OneShotEffect;
|
||||
import mage.abilities.effects.common.RoomCharacteristicsEffect;
|
||||
import mage.constants.CardType;
|
||||
import mage.constants.Duration;
|
||||
import mage.constants.Outcome;
|
||||
import mage.constants.SpellAbilityType;
|
||||
import mage.constants.Zone;
|
||||
import mage.game.Game;
|
||||
import mage.game.permanent.Permanent;
|
||||
import mage.game.permanent.PermanentToken;
|
||||
import mage.abilities.effects.common.continuous.LoseAbilitySourceEffect;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* @author oscscull
|
||||
|
|
@ -43,6 +36,13 @@ public abstract class RoomCard extends SplitCard {
|
|||
this.getOwnerId(), new CardSetInfo(names[1], setInfo.getExpansionSetCode(), setInfo.getCardNumber(),
|
||||
setInfo.getRarity(), setInfo.getGraphicInfo()),
|
||||
types, costsRight, this, SpellAbilityType.SPLIT_RIGHT);
|
||||
|
||||
// Add the one-shot effect to unlock a door on cast -> ETB
|
||||
Ability entersAbility = new EntersBattlefieldAbility(new RoomEnterUnlockEffect());
|
||||
entersAbility.setRuleVisible(false);
|
||||
this.addAbility(entersAbility);
|
||||
|
||||
this.addAbility(new RoomAbility());
|
||||
}
|
||||
|
||||
protected RoomCard(RoomCard card) {
|
||||
|
|
@ -58,56 +58,6 @@ public abstract class RoomCard extends SplitCard {
|
|||
this.lastCastHalf = lastCastHalf;
|
||||
}
|
||||
|
||||
protected void addRoomAbilities(Ability leftAbility, Ability rightAbility) {
|
||||
getLeftHalfCard().addAbility(leftAbility);
|
||||
getRightHalfCard().addAbility(rightAbility);
|
||||
this.addAbility(leftAbility.copy());
|
||||
this.addAbility(rightAbility.copy());
|
||||
|
||||
// Add the one-shot effect to unlock a door on cast -> ETB
|
||||
Ability entersAbility = new EntersBattlefieldAbility(new RoomEnterUnlockEffect());
|
||||
entersAbility.setRuleVisible(false);
|
||||
this.addAbility(entersAbility);
|
||||
|
||||
// Remove locked door abilities - keeping unlock triggers (or they won't trigger
|
||||
// when unlocked)
|
||||
if (leftAbility != null && !(leftAbility instanceof UnlockThisDoorTriggeredAbility)) {
|
||||
Ability ability = new SimpleStaticAbility(Zone.BATTLEFIELD, new ConditionalContinuousEffect(
|
||||
new LoseAbilitySourceEffect(leftAbility, Duration.WhileOnBattlefield),
|
||||
RoomHalfLockedCondition.LEFT, "")).setRuleVisible(false);
|
||||
this.addAbility(ability);
|
||||
}
|
||||
|
||||
if (rightAbility != null && !(rightAbility instanceof UnlockThisDoorTriggeredAbility)) {
|
||||
Ability ability = new SimpleStaticAbility(Zone.BATTLEFIELD, new ConditionalContinuousEffect(
|
||||
new LoseAbilitySourceEffect(rightAbility, Duration.WhileOnBattlefield),
|
||||
RoomHalfLockedCondition.RIGHT, "")).setRuleVisible(false);
|
||||
this.addAbility(ability);
|
||||
}
|
||||
|
||||
// Add the Special Action to unlock doors.
|
||||
// These will ONLY be active if the corresponding half is LOCKED!
|
||||
if (leftAbility != null) {
|
||||
ManaCosts leftHalfManaCost = null;
|
||||
if (this.getLeftHalfCard() != null && this.getLeftHalfCard().getSpellAbility() != null) {
|
||||
leftHalfManaCost = this.getLeftHalfCard().getSpellAbility().getManaCosts();
|
||||
}
|
||||
RoomUnlockAbility leftUnlockAbility = new RoomUnlockAbility(leftHalfManaCost, true);
|
||||
this.addAbility(leftUnlockAbility.setRuleAtTheTop(true));
|
||||
}
|
||||
|
||||
if (rightAbility != null) {
|
||||
ManaCosts rightHalfManaCost = null;
|
||||
if (this.getRightHalfCard() != null && this.getRightHalfCard().getSpellAbility() != null) {
|
||||
rightHalfManaCost = this.getRightHalfCard().getSpellAbility().getManaCosts();
|
||||
}
|
||||
RoomUnlockAbility rightUnlockAbility = new RoomUnlockAbility(rightHalfManaCost, false);
|
||||
this.addAbility(rightUnlockAbility.setRuleAtTheTop(true));
|
||||
}
|
||||
|
||||
this.addAbility(new RoomAbility());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Abilities<Ability> getAbilities() {
|
||||
return this.abilities;
|
||||
|
|
@ -131,6 +81,41 @@ public abstract class RoomCard extends SplitCard {
|
|||
game.setZone(getLeftHalfCard().getId(), zone);
|
||||
game.setZone(getRightHalfCard().getId(), zone);
|
||||
}
|
||||
|
||||
public static void setRoomCharacteristics(Permanent permanent, Game game) {
|
||||
if (!(permanent.getMainCard() instanceof RoomCard)) {
|
||||
return;
|
||||
}
|
||||
setRoomCharacteristics(permanent, (RoomCard) permanent.getMainCard(), game, permanent.isLeftDoorUnlocked(), permanent.isRightDoorUnlocked());
|
||||
}
|
||||
|
||||
// Static method for setting room characteristics on permanents
|
||||
public static void setRoomCharacteristics(Permanent permanent, RoomCard roomCard, Game game, boolean isLeftUnlocked, boolean isRightUnlocked) {
|
||||
permanent.setName(roomCard.name);
|
||||
|
||||
permanent.setManaCost(roomCard.getManaCost());
|
||||
|
||||
// Set color indicator based on unlocked halves
|
||||
ObjectColor newColor = new ObjectColor();
|
||||
if (isLeftUnlocked && roomCard.getLeftHalfCard() != null) {
|
||||
newColor.addColor(roomCard.getLeftHalfCard().getColor());
|
||||
}
|
||||
if (isRightUnlocked && roomCard.getRightHalfCard() != null) {
|
||||
newColor.addColor(roomCard.getRightHalfCard().getColor());
|
||||
}
|
||||
permanent.getColor().setColor(roomCard.getColor());
|
||||
|
||||
// Get abilities from each half
|
||||
Abilities<Ability> leftAbilities = roomCard.getLeftHalfCard().getAbilities();
|
||||
for (Ability ability : leftAbilities) {
|
||||
permanent.addAbility(ability, roomCard.getLeftHalfCard().getId(), game, true);
|
||||
}
|
||||
|
||||
Abilities<Ability> rightAbilities = roomCard.getRightHalfCard().getAbilities();
|
||||
for (Ability ability : rightAbilities) {
|
||||
permanent.addAbility(ability, roomCard.getRightHalfCard().getId(), game,true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class RoomEnterUnlockEffect extends OneShotEffect {
|
||||
|
|
@ -189,27 +174,3 @@ class RoomEnterUnlockEffect extends OneShotEffect {
|
|||
}
|
||||
}
|
||||
|
||||
// For the overall Room card flavor text and mana value effect.
|
||||
class RoomAbility extends SimpleStaticAbility {
|
||||
public RoomAbility() {
|
||||
super(Zone.ALL, null);
|
||||
this.setRuleVisible(true);
|
||||
this.setRuleAtTheTop(true);
|
||||
this.addEffect(new RoomCharacteristicsEffect());
|
||||
}
|
||||
|
||||
protected RoomAbility(final RoomAbility ability) {
|
||||
super(ability);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRule() {
|
||||
return "<i>(You may cast either half. That door unlocks on the battlefield. " +
|
||||
"As a sorcery, you may pay the mana cost of a locked door to unlock it.)</i>";
|
||||
}
|
||||
|
||||
@Override
|
||||
public RoomAbility copy() {
|
||||
return new RoomAbility(this);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
package mage.cards;
|
||||
|
||||
import mage.abilities.SpellAbility;
|
||||
import mage.constants.*;
|
||||
import mage.game.Game;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public abstract class TransformingDoubleFacedCard extends DoubleFacedCard {
|
||||
|
||||
public TransformingDoubleFacedCard(
|
||||
UUID ownerId, CardSetInfo setInfo,
|
||||
CardType[] typesLeft, SubType[] subTypesLeft, String costsLeft,
|
||||
String secondSideName,
|
||||
CardType[] typesRight, SubType[] subTypesRight, String colorRight
|
||||
) {
|
||||
this(
|
||||
ownerId, setInfo,
|
||||
new SuperType[]{}, typesLeft, subTypesLeft, costsLeft,
|
||||
secondSideName,
|
||||
new SuperType[]{}, typesRight, subTypesRight, colorRight
|
||||
);
|
||||
}
|
||||
|
||||
public TransformingDoubleFacedCard(
|
||||
UUID ownerId, CardSetInfo setInfo,
|
||||
SuperType[] superTypesLeft, CardType[] typesLeft, SubType[] subTypesLeft, String costsLeft,
|
||||
String secondSideName,
|
||||
SuperType[] superTypesRight, CardType[] typesRight, SubType[] subTypesRight, String colorRight
|
||||
) {
|
||||
super(ownerId, setInfo, typesLeft, costsLeft, SpellAbilityType.TRANSFORMED);
|
||||
// main card name must be same as left side
|
||||
leftHalfCard = new TransformingDoubleFacedCardHalf(
|
||||
this.getOwnerId(), setInfo.copy(),
|
||||
superTypesLeft, typesLeft, subTypesLeft, costsLeft,
|
||||
this, SpellAbilityType.TRANSFORMED_LEFT
|
||||
);
|
||||
rightHalfCard = new TransformingDoubleFacedCardHalf(
|
||||
this.getOwnerId(), new CardSetInfo(secondSideName, setInfo),
|
||||
superTypesRight, typesRight, subTypesRight, colorRight, this
|
||||
);
|
||||
this.secondSideCard = rightHalfCard;
|
||||
}
|
||||
|
||||
public TransformingDoubleFacedCard(final TransformingDoubleFacedCard card) {
|
||||
super(card);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean cast(Game game, Zone fromZone, SpellAbility ability, UUID controllerId) {
|
||||
if (ability.getSpellAbilityType() == SpellAbilityType.BASE) {
|
||||
return this.leftHalfCard.cast(game, fromZone, ability, controllerId);
|
||||
}
|
||||
return super.cast(game, fromZone, ability, controllerId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TransformingDoubleFacedCardHalf getLeftHalfCard() {
|
||||
return (TransformingDoubleFacedCardHalf) leftHalfCard;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TransformingDoubleFacedCardHalf getRightHalfCard() {
|
||||
return (TransformingDoubleFacedCardHalf) rightHalfCard;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
package mage.cards;
|
||||
|
||||
import mage.ObjectColor;
|
||||
import mage.abilities.SpellAbility;
|
||||
import mage.constants.*;
|
||||
import mage.game.Game;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public class TransformingDoubleFacedCardHalf extends DoubleFacedCardHalf {
|
||||
|
||||
public TransformingDoubleFacedCardHalf(
|
||||
UUID ownerId, CardSetInfo setInfo,
|
||||
SuperType[] cardSuperTypes, CardType[] cardTypes, SubType[] cardSubTypes,
|
||||
String costs, TransformingDoubleFacedCard parentCard, SpellAbilityType spellAbilityType
|
||||
) {
|
||||
super(ownerId, setInfo, cardSuperTypes, cardTypes, cardSubTypes, costs, parentCard, spellAbilityType);
|
||||
}
|
||||
|
||||
protected TransformingDoubleFacedCardHalf(final TransformingDoubleFacedCardHalf card) {
|
||||
super(card);
|
||||
this.parentCard = card.parentCard;
|
||||
}
|
||||
|
||||
public TransformingDoubleFacedCardHalf(
|
||||
UUID ownerId, CardSetInfo setInfo,
|
||||
SuperType[] superTypesRight, CardType[] typesRight, SubType[] subTypesRight, String colorRight, TransformingDoubleFacedCard parentCard) {
|
||||
super(ownerId, setInfo, superTypesRight, typesRight, subTypesRight, "", parentCard, SpellAbilityType.TRANSFORMED_RIGHT);
|
||||
this.getColor().setColor(new ObjectColor(colorRight));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean cast(Game game, Zone fromZone, SpellAbility ability, UUID controllerId) {
|
||||
if (ability.getSpellAbilityCastMode() == SpellAbilityCastMode.DISTURB && !isBackSide()) {
|
||||
return getOtherSide().cast(game, fromZone, ability, controllerId);
|
||||
}
|
||||
return super.cast(game, fromZone, ability, controllerId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TransformingDoubleFacedCardHalf copy() {
|
||||
return new TransformingDoubleFacedCardHalf(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TransformingDoubleFacedCard getParentCard() {
|
||||
return (TransformingDoubleFacedCard) parentCard;
|
||||
}
|
||||
}
|
||||
|
|
@ -5,7 +5,7 @@ import mage.abilities.Ability;
|
|||
import mage.abilities.costs.mana.ManaCost;
|
||||
import mage.abilities.costs.mana.ManaCosts;
|
||||
import mage.cards.CardImpl;
|
||||
import mage.cards.ModalDoubleFacedCard;
|
||||
import mage.cards.DoubleFacedCard;
|
||||
import mage.cards.repository.CardInfo;
|
||||
import mage.cards.repository.CardRepository;
|
||||
import mage.util.CardUtil;
|
||||
|
|
@ -35,7 +35,7 @@ public class MockCard extends CardImpl implements MockableCard {
|
|||
protected List<String> manaCostRightStr;
|
||||
protected List<String> manaCostStr;
|
||||
protected String spellOptionName; // adventure/omen spell name
|
||||
protected boolean isModalDoubleFacedCard;
|
||||
protected boolean isDoubleFacedCard;
|
||||
protected int manaValue;
|
||||
|
||||
public MockCard(CardInfo card) {
|
||||
|
|
@ -67,7 +67,7 @@ public class MockCard extends CardImpl implements MockableCard {
|
|||
|
||||
this.nightCard = card.isNightCard();
|
||||
|
||||
if (card.getSecondSideName() != null && !card.getSecondSideName().isEmpty()) {
|
||||
if (card.getSecondSideName() != null && !card.getSecondSideName().isEmpty() && !card.isDoubleFacedCard()) {
|
||||
this.secondSideCard = new MockCard(CardRepository.instance.findCardWithPreferredSetAndNumber(card.getSecondSideName(), card.getSetCode(), card.getCardNumber()));
|
||||
}
|
||||
|
||||
|
|
@ -75,11 +75,11 @@ public class MockCard extends CardImpl implements MockableCard {
|
|||
this.spellOptionName = card.getSpellOptionCardName();
|
||||
}
|
||||
|
||||
if (card.isModalDoubleFacedCard()) {
|
||||
ModalDoubleFacedCard mdfCard = (ModalDoubleFacedCard) card.createCard();
|
||||
if (card.isDoubleFacedCard()) {
|
||||
DoubleFacedCard mdfCard = (DoubleFacedCard) card.createCard();
|
||||
CardInfo mdfSecondSide = new CardInfo(mdfCard.getRightHalfCard());
|
||||
this.secondSideCard = new MockCard(mdfSecondSide);
|
||||
this.isModalDoubleFacedCard = true;
|
||||
this.isDoubleFacedCard = true;
|
||||
}
|
||||
|
||||
this.startingLoyalty = CardUtil.convertLoyaltyOrDefense(card.getStartingLoyalty());
|
||||
|
|
@ -102,7 +102,7 @@ public class MockCard extends CardImpl implements MockableCard {
|
|||
this.manaCostRightStr = new ArrayList<>(card.manaCostRightStr);
|
||||
this.manaCostStr = new ArrayList<>(card.manaCostStr);
|
||||
this.spellOptionName = card.spellOptionName;
|
||||
this.isModalDoubleFacedCard = card.isModalDoubleFacedCard;
|
||||
this.isDoubleFacedCard = card.isDoubleFacedCard;
|
||||
this.manaValue = card.manaValue;
|
||||
}
|
||||
|
||||
|
|
@ -157,7 +157,7 @@ public class MockCard extends CardImpl implements MockableCard {
|
|||
|
||||
if (spellOptionName != null) {
|
||||
return getName() + CARD_WITH_SPELL_OPTION_NAME_SEPARATOR + spellOptionName;
|
||||
} else if (isModalDoubleFacedCard) {
|
||||
} else if (isDoubleFacedCard) {
|
||||
return getName() + MODAL_DOUBLE_FACES_NAME_SEPARATOR + this.getSecondCardFace().getName();
|
||||
} else {
|
||||
return getName();
|
||||
|
|
@ -181,6 +181,6 @@ public class MockCard extends CardImpl implements MockableCard {
|
|||
@Override
|
||||
public boolean isTransformable() {
|
||||
// must enable toggle mode in deck editor (switch between card sides);
|
||||
return super.isTransformable() || this.isModalDoubleFacedCard || this.secondSideCard != null;
|
||||
return super.isTransformable() || this.isDoubleFacedCard || this.secondSideCard != null;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -110,9 +110,9 @@ public class CardInfo {
|
|||
@DatabaseField
|
||||
protected String spellOptionCardName;
|
||||
@DatabaseField
|
||||
protected boolean modalDoubleFacedCard;
|
||||
protected boolean doubleFacedCard;
|
||||
@DatabaseField
|
||||
protected String modalDoubleFacedSecondSideName;
|
||||
protected String doubleFacedSecondSideName;
|
||||
@DatabaseField
|
||||
protected String meldsToCardName;
|
||||
@DatabaseField
|
||||
|
|
@ -162,9 +162,9 @@ public class CardInfo {
|
|||
this.spellOptionCardName = ((CardWithSpellOption) card).getSpellCard().getName();
|
||||
}
|
||||
|
||||
if (card instanceof ModalDoubleFacedCard) {
|
||||
this.modalDoubleFacedCard = true;
|
||||
this.modalDoubleFacedSecondSideName = ((ModalDoubleFacedCard) card).getRightHalfCard().getName();
|
||||
if (card instanceof DoubleFacedCard) {
|
||||
this.doubleFacedCard = true;
|
||||
this.doubleFacedSecondSideName = ((DoubleFacedCard) card).getRightHalfCard().getName();
|
||||
}
|
||||
|
||||
if (card.getFrameStyle() != null) {
|
||||
|
|
@ -483,12 +483,12 @@ public class CardInfo {
|
|||
return spellOptionCardName;
|
||||
}
|
||||
|
||||
public boolean isModalDoubleFacedCard() {
|
||||
return modalDoubleFacedCard;
|
||||
public boolean isDoubleFacedCard() {
|
||||
return doubleFacedCard;
|
||||
}
|
||||
|
||||
public String getModalDoubleFacedSecondSideName() {
|
||||
return modalDoubleFacedSecondSideName;
|
||||
public String getDoubleFacedSecondSideName() {
|
||||
return doubleFacedSecondSideName;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -126,7 +126,7 @@ public enum CardRepository {
|
|||
}
|
||||
|
||||
private void addNewNames(CardInfo card, Set<String> namesList) {
|
||||
// require before call: qb.distinct().selectColumns("name", "modalDoubleFacedSecondSideName"...);
|
||||
// require before call: qb.distinct().selectColumns("name", "doubleFacedSecondSideName"...);
|
||||
|
||||
// normal names
|
||||
int result = card.getName().indexOf(" // ");
|
||||
|
|
@ -141,8 +141,8 @@ public enum CardRepository {
|
|||
if (card.getSecondSideName() != null && !card.getSecondSideName().isEmpty()) {
|
||||
namesList.add(card.getSecondSideName());
|
||||
}
|
||||
if (card.getModalDoubleFacedSecondSideName() != null && !card.getModalDoubleFacedSecondSideName().isEmpty()) {
|
||||
namesList.add(card.getModalDoubleFacedSecondSideName());
|
||||
if (card.getDoubleFacedSecondSideName() != null && !card.getDoubleFacedSecondSideName().isEmpty()) {
|
||||
namesList.add(card.getDoubleFacedSecondSideName());
|
||||
}
|
||||
if (card.getFlipCardName() != null && !card.getFlipCardName().isEmpty()) {
|
||||
namesList.add(card.getFlipCardName());
|
||||
|
|
@ -166,7 +166,7 @@ public enum CardRepository {
|
|||
}
|
||||
try {
|
||||
QueryBuilder<CardInfo, Object> qb = cardsDao.queryBuilder();
|
||||
qb.distinct().selectColumns("name", "modalDoubleFacedSecondSideName", "secondSideName", "flipCardName", "spellOptionCardName");
|
||||
qb.distinct().selectColumns("name", "doubleFacedSecondSideName", "secondSideName", "flipCardName", "spellOptionCardName");
|
||||
List<CardInfo> results = cardsDao.query(qb.prepare());
|
||||
for (CardInfo card : results) {
|
||||
addNewNames(card, names);
|
||||
|
|
@ -185,7 +185,7 @@ public enum CardRepository {
|
|||
}
|
||||
try {
|
||||
QueryBuilder<CardInfo, Object> qb = cardsDao.queryBuilder();
|
||||
qb.distinct().selectColumns("name", "modalDoubleFacedSecondSideName", "secondSideName", "flipCardName", "spellOptionCardName");
|
||||
qb.distinct().selectColumns("name", "doubleFacedSecondSideName", "secondSideName", "flipCardName", "spellOptionCardName");
|
||||
qb.where().not().like("types", new SelectArg('%' + CardType.LAND.name() + '%'));
|
||||
List<CardInfo> results = cardsDao.query(qb.prepare());
|
||||
for (CardInfo card : results) {
|
||||
|
|
@ -205,7 +205,7 @@ public enum CardRepository {
|
|||
}
|
||||
try {
|
||||
QueryBuilder<CardInfo, Object> qb = cardsDao.queryBuilder();
|
||||
qb.distinct().selectColumns("name", "modalDoubleFacedSecondSideName", "secondSideName", "flipCardName", "spellOptionCardName");
|
||||
qb.distinct().selectColumns("name", "doubleFacedSecondSideName", "secondSideName", "flipCardName", "spellOptionCardName");
|
||||
Where<CardInfo, Object> where = qb.where();
|
||||
where.and(
|
||||
where.not().like("supertypes", '%' + SuperType.BASIC.name() + '%'),
|
||||
|
|
@ -229,7 +229,7 @@ public enum CardRepository {
|
|||
}
|
||||
try {
|
||||
QueryBuilder<CardInfo, Object> qb = cardsDao.queryBuilder();
|
||||
qb.distinct().selectColumns("name", "modalDoubleFacedSecondSideName", "secondSideName", "flipCardName", "spellOptionCardName");
|
||||
qb.distinct().selectColumns("name", "doubleFacedSecondSideName", "secondSideName", "flipCardName", "spellOptionCardName");
|
||||
qb.where().not().like("supertypes", new SelectArg('%' + SuperType.BASIC.name() + '%'));
|
||||
List<CardInfo> results = cardsDao.query(qb.prepare());
|
||||
for (CardInfo card : results) {
|
||||
|
|
@ -249,7 +249,7 @@ public enum CardRepository {
|
|||
}
|
||||
try {
|
||||
QueryBuilder<CardInfo, Object> qb = cardsDao.queryBuilder();
|
||||
qb.distinct().selectColumns("name", "modalDoubleFacedSecondSideName", "secondSideName", "flipCardName", "spellOptionCardName");
|
||||
qb.distinct().selectColumns("name", "doubleFacedSecondSideName", "secondSideName", "flipCardName", "spellOptionCardName");
|
||||
qb.where().like("types", new SelectArg('%' + CardType.CREATURE.name() + '%'));
|
||||
List<CardInfo> results = cardsDao.query(qb.prepare());
|
||||
for (CardInfo card : results) {
|
||||
|
|
@ -269,7 +269,7 @@ public enum CardRepository {
|
|||
}
|
||||
try {
|
||||
QueryBuilder<CardInfo, Object> qb = cardsDao.queryBuilder();
|
||||
qb.distinct().selectColumns("name", "modalDoubleFacedSecondSideName", "secondSideName", "flipCardName", "spellOptionCardName");
|
||||
qb.distinct().selectColumns("name", "doubleFacedSecondSideName", "secondSideName", "flipCardName", "spellOptionCardName");
|
||||
qb.where().like("types", new SelectArg('%' + CardType.ARTIFACT.name() + '%'));
|
||||
List<CardInfo> results = cardsDao.query(qb.prepare());
|
||||
for (CardInfo card : results) {
|
||||
|
|
@ -289,7 +289,7 @@ public enum CardRepository {
|
|||
}
|
||||
try {
|
||||
QueryBuilder<CardInfo, Object> qb = cardsDao.queryBuilder();
|
||||
qb.distinct().selectColumns("name", "modalDoubleFacedSecondSideName", "secondSideName", "flipCardName", "spellOptionCardName");
|
||||
qb.distinct().selectColumns("name", "doubleFacedSecondSideName", "secondSideName", "flipCardName", "spellOptionCardName");
|
||||
Where<CardInfo, Object> where = qb.where();
|
||||
where.and(
|
||||
where.not().like("types", '%' + CardType.CREATURE.name() + '%'),
|
||||
|
|
@ -313,7 +313,7 @@ public enum CardRepository {
|
|||
}
|
||||
try {
|
||||
QueryBuilder<CardInfo, Object> qb = cardsDao.queryBuilder();
|
||||
qb.distinct().selectColumns("name", "modalDoubleFacedSecondSideName", "secondSideName", "flipCardName", "spellOptionCardName");
|
||||
qb.distinct().selectColumns("name", "doubleFacedSecondSideName", "secondSideName", "flipCardName", "spellOptionCardName");
|
||||
Where<CardInfo, Object> where = qb.where();
|
||||
where.and(
|
||||
where.not().like("types", '%' + CardType.ARTIFACT.name() + '%'),
|
||||
|
|
@ -539,7 +539,7 @@ public enum CardRepository {
|
|||
.eq("flipCardName", new SelectArg(name)).or()
|
||||
.eq("secondSideName", new SelectArg(name)).or()
|
||||
.eq("spellOptionCardName", new SelectArg(name)).or()
|
||||
.eq("modalDoubleFacedSecondSideName", new SelectArg(name));
|
||||
.eq("doubleFacedSecondSideName", new SelectArg(name));
|
||||
results = cardsDao.query(queryBuilder.prepare());
|
||||
} else {
|
||||
// Check that a full card was found and not a SplitCardHalf
|
||||
|
|
|
|||
|
|
@ -2,9 +2,7 @@ package mage.constants;
|
|||
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.keyword.TransformAbility;
|
||||
import mage.cards.Card;
|
||||
import mage.cards.Cards;
|
||||
import mage.cards.CardsImpl;
|
||||
import mage.cards.*;
|
||||
import mage.game.Game;
|
||||
import mage.game.permanent.Permanent;
|
||||
import mage.players.Player;
|
||||
|
|
@ -92,6 +90,9 @@ public enum PutCards {
|
|||
case SHUFFLE:
|
||||
return player.shuffleCardsToLibrary(card, game, source);
|
||||
case BATTLEFIELD_TRANSFORMED:
|
||||
if (card instanceof TransformingDoubleFacedCard) {
|
||||
card = ((TransformingDoubleFacedCard) card).getRightHalfCard();
|
||||
}
|
||||
game.getState().setValue(TransformAbility.VALUE_KEY_ENTER_TRANSFORMED + card.getId(), Boolean.TRUE);
|
||||
case BATTLEFIELD:
|
||||
case EXILED:
|
||||
|
|
|
|||
|
|
@ -11,6 +11,9 @@ public enum SpellAbilityType {
|
|||
SPLIT_FUSED("Split SpellAbility"),
|
||||
SPLIT_LEFT("LeftSplit SpellAbility"),
|
||||
SPLIT_RIGHT("RightSplit SpellAbility"),
|
||||
TRANSFORMED("Transformed SpellAbility"),
|
||||
TRANSFORMED_LEFT("TransformFront SpellAbility"),
|
||||
TRANSFORMED_RIGHT("TransformBack SpellAbility"),
|
||||
MODAL("Modal SpellAbility"), // used for modal double faces cards
|
||||
MODAL_LEFT("LeftModal SpellAbility"),
|
||||
MODAL_RIGHT("RightModal SpellAbility"),
|
||||
|
|
|
|||
|
|
@ -56,6 +56,7 @@ import mage.game.mulligan.Mulligan;
|
|||
import mage.game.permanent.Battlefield;
|
||||
import mage.game.permanent.Permanent;
|
||||
import mage.game.permanent.PermanentCard;
|
||||
import mage.game.permanent.PermanentToken;
|
||||
import mage.game.stack.Spell;
|
||||
import mage.game.stack.SpellStack;
|
||||
import mage.game.stack.StackAbility;
|
||||
|
|
@ -129,8 +130,6 @@ public abstract class GameImpl implements Game {
|
|||
// For checking "becomes the target" triggers accurately. Cleared on short living LKI reset
|
||||
protected Map<String, Map<UUID, Set<UUID>>> targetedMap = new HashMap<>();
|
||||
|
||||
// Permanents entering the Battlefield while handling replacement effects before they are added to the battlefield
|
||||
protected Map<UUID, Permanent> permanentsEntering = new HashMap<>();
|
||||
// used to set the counters a permanent adds the battlefield (if no replacement effect is used e.g. Persist)
|
||||
protected Map<UUID, Counters> enterWithCounters = new HashMap<>();
|
||||
|
||||
|
|
@ -214,7 +213,6 @@ public abstract class GameImpl implements Game {
|
|||
this.lkiShortLiving = CardUtil.deepCopyObject(game.lkiShortLiving);
|
||||
this.targetedMap = CardUtil.deepCopyObject(game.targetedMap);
|
||||
|
||||
this.permanentsEntering = CardUtil.deepCopyObject(game.permanentsEntering);
|
||||
this.enterWithCounters = CardUtil.deepCopyObject(game.enterWithCounters);
|
||||
|
||||
this.state = game.state.copy();
|
||||
|
|
@ -341,13 +339,13 @@ public abstract class GameImpl implements Game {
|
|||
Card rightCard = ((SplitCard) card).getRightHalfCard();
|
||||
rightCard.setOwnerId(ownerId);
|
||||
addCardToState(rightCard);
|
||||
} else if (card instanceof ModalDoubleFacedCard) {
|
||||
} else if (card instanceof DoubleFacedCard) {
|
||||
// left
|
||||
Card leftCard = ((ModalDoubleFacedCard) card).getLeftHalfCard();
|
||||
Card leftCard = ((DoubleFacedCard) card).getLeftHalfCard();
|
||||
leftCard.setOwnerId(ownerId);
|
||||
addCardToState(leftCard);
|
||||
// right
|
||||
Card rightCard = ((ModalDoubleFacedCard) card).getRightHalfCard();
|
||||
Card rightCard = ((DoubleFacedCard) card).getRightHalfCard();
|
||||
rightCard.setOwnerId(ownerId);
|
||||
addCardToState(rightCard);
|
||||
} else if (card instanceof CardWithSpellOption) {
|
||||
|
|
@ -764,12 +762,12 @@ public abstract class GameImpl implements Game {
|
|||
|
||||
@Override
|
||||
public Permanent getPermanentEntering(UUID permanentId) {
|
||||
return permanentsEntering.get(permanentId);
|
||||
return state.getBattlefield().getPermanentsEntering().get(permanentId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<UUID, Permanent> getPermanentsEntering() {
|
||||
return permanentsEntering;
|
||||
return state.getBattlefield().getPermanentsEntering();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -2109,6 +2107,7 @@ public abstract class GameImpl implements Game {
|
|||
newBluePrint = copyFromPermanent.copy();
|
||||
|
||||
// reset to original characteristics
|
||||
newBluePrint.resetLockedStatus(); // reset locked status so room characteristics are correct
|
||||
newBluePrint.reset(this);
|
||||
|
||||
// workaround to find real copyable characteristics of transformed/facedown/etc permanents
|
||||
|
|
@ -2118,7 +2117,9 @@ public abstract class GameImpl implements Game {
|
|||
BecomesFaceDownCreatureEffect.makeFaceDownObject(this, null, newBluePrint, faceDownType, null);
|
||||
}
|
||||
newBluePrint.assignNewId();
|
||||
if (copyFromPermanent.isTransformed()) {
|
||||
// TODO: should be able to remove after tdfc rework
|
||||
if (copyFromPermanent.isTransformed() && (copyFromPermanent instanceof PermanentToken || ((copyFromPermanent instanceof PermanentCard) &&
|
||||
!(((PermanentCard) copyFromPermanent).getCard() instanceof DoubleFacedCardHalf)))) {
|
||||
TransformAbility.transformPermanent(newBluePrint, this, source);
|
||||
}
|
||||
if (copyFromPermanent.isPrototyped()) {
|
||||
|
|
@ -3818,7 +3819,7 @@ public abstract class GameImpl implements Game {
|
|||
loadCards(ownerId, hand);
|
||||
loadCards(ownerId, battlefield
|
||||
.stream()
|
||||
.map(PutToBattlefieldInfo::getCard)
|
||||
.map(PutToBattlefieldInfo::getMainCard)
|
||||
.collect(Collectors.toList())
|
||||
);
|
||||
loadCards(ownerId, graveyard);
|
||||
|
|
|
|||
|
|
@ -670,9 +670,9 @@ public class GameState implements Serializable, Copyable<GameState> {
|
|||
for (Player player : players.values()) {
|
||||
player.reset();
|
||||
}
|
||||
this.reset();
|
||||
battlefield.reset(game);
|
||||
combat.reset(game);
|
||||
this.reset();
|
||||
effects.apply(game);
|
||||
combat.checkForRemoveFromCombat(game);
|
||||
}
|
||||
|
|
@ -1627,19 +1627,19 @@ public class GameState implements Serializable, Copyable<GameState> {
|
|||
copiedParts.add(rightCopied);
|
||||
// sync parts
|
||||
((SplitCard) copiedCard).setParts(leftCopied, rightCopied);
|
||||
} else if (copiedCard instanceof ModalDoubleFacedCard) {
|
||||
} else if (copiedCard instanceof DoubleFacedCard) {
|
||||
// left
|
||||
ModalDoubleFacedCardHalf leftOriginal = ((ModalDoubleFacedCard) copiedCard).getLeftHalfCard();
|
||||
ModalDoubleFacedCardHalf leftCopied = leftOriginal.copy();
|
||||
DoubleFacedCardHalf leftOriginal = ((DoubleFacedCard) copiedCard).getLeftHalfCard();
|
||||
DoubleFacedCardHalf leftCopied = (DoubleFacedCardHalf) leftOriginal.copy();
|
||||
prepareCardForCopy(leftOriginal, leftCopied, newController);
|
||||
copiedParts.add(leftCopied);
|
||||
// right
|
||||
ModalDoubleFacedCardHalf rightOriginal = ((ModalDoubleFacedCard) copiedCard).getRightHalfCard();
|
||||
ModalDoubleFacedCardHalf rightCopied = rightOriginal.copy();
|
||||
DoubleFacedCardHalf rightOriginal = ((DoubleFacedCard) copiedCard).getRightHalfCard();
|
||||
DoubleFacedCardHalf rightCopied = (DoubleFacedCardHalf) rightOriginal.copy();
|
||||
prepareCardForCopy(rightOriginal, rightCopied, newController);
|
||||
copiedParts.add(rightCopied);
|
||||
// sync parts
|
||||
((ModalDoubleFacedCard) copiedCard).setParts(leftCopied, rightCopied);
|
||||
((DoubleFacedCard) copiedCard).setParts(leftCopied, rightCopied);
|
||||
} else if (copiedCard instanceof CardWithSpellOption) {
|
||||
// right
|
||||
SpellOptionCard rightOriginal = ((CardWithSpellOption) copiedCard).getSpellCard();
|
||||
|
|
|
|||
|
|
@ -21,6 +21,10 @@ public class PutToBattlefieldInfo {
|
|||
return card;
|
||||
}
|
||||
|
||||
public Card getMainCard() {
|
||||
return card.getMainCard();
|
||||
}
|
||||
|
||||
public boolean isTapped() {
|
||||
return tapped;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -89,21 +89,31 @@ public final class ZonesHandler {
|
|||
ZoneChangeInfo info = itr.next();
|
||||
if (info.event.getToZone().equals(Zone.BATTLEFIELD)) {
|
||||
Card card = game.getCard(info.event.getTargetId());
|
||||
if (card instanceof ModalDoubleFacedCard || card instanceof ModalDoubleFacedCardHalf) {
|
||||
if (card instanceof DoubleFacedCard || card instanceof DoubleFacedCardHalf) {
|
||||
boolean forceToMainSide = false;
|
||||
// TODO: move transform key or have some other identifier after tdfc rework
|
||||
Boolean enterTransformed = (Boolean) game.getState().getValue(TransformAbility.VALUE_KEY_ENTER_TRANSFORMED + card.getId());
|
||||
if (enterTransformed == null) {
|
||||
enterTransformed = false;
|
||||
}
|
||||
|
||||
// if effect put half mdf card to battlefield then it must be the main side only (example: return targeted half card to battle)
|
||||
if (card instanceof ModalDoubleFacedCardHalf && !source.getAbilityType().isPlayCardAbility()) {
|
||||
if (card instanceof DoubleFacedCardHalf && !source.getAbilityType().isPlayCardAbility() && !enterTransformed) {
|
||||
forceToMainSide = true;
|
||||
}
|
||||
|
||||
// if effect put mdf card to battlefield then it must be main side only
|
||||
if (card instanceof ModalDoubleFacedCard) {
|
||||
if (card instanceof DoubleFacedCard) {
|
||||
forceToMainSide = true;
|
||||
}
|
||||
|
||||
if (forceToMainSide) {
|
||||
info.event.setTargetId(((ModalDoubleFacedCard) card.getMainCard()).getLeftHalfCard().getId());
|
||||
info.event.setTargetId(((DoubleFacedCard) card.getMainCard()).getLeftHalfCard().getId());
|
||||
}
|
||||
|
||||
// if left half is being moved, but entering transformed, change to transformed side
|
||||
if (enterTransformed && card instanceof DoubleFacedCardHalf && !((DoubleFacedCardHalf) card).isBackSide()) {
|
||||
info.event.setTargetId(((DoubleFacedCardHalf) card).getOtherSide().getId());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -154,10 +164,10 @@ public final class ZonesHandler {
|
|||
// meld/group cards must be independent (use can choose order)
|
||||
cardsToMove = ((MeldCard) targetCard).getHalves();
|
||||
cardsToUpdate.get(toZone).addAll(cardsToMove);
|
||||
} else if (targetCard instanceof ModalDoubleFacedCard
|
||||
|| targetCard instanceof ModalDoubleFacedCardHalf) {
|
||||
} else if (targetCard instanceof DoubleFacedCard
|
||||
|| targetCard instanceof DoubleFacedCardHalf) {
|
||||
// mdf cards must be moved as single object, but each half must be updated separately
|
||||
ModalDoubleFacedCard mdfCard = (ModalDoubleFacedCard) targetCard.getMainCard();
|
||||
DoubleFacedCard mdfCard = (DoubleFacedCard) targetCard.getMainCard();
|
||||
cardsToMove = new CardsImpl(mdfCard);
|
||||
cardsToUpdate.get(toZone).add(mdfCard);
|
||||
// example: cast left side
|
||||
|
|
@ -296,7 +306,7 @@ public final class ZonesHandler {
|
|||
} else {
|
||||
game.setZone(event.getTargetId(), event.getToZone());
|
||||
}
|
||||
|
||||
|
||||
// update zone in other parts (meld cards, mdf half cards)
|
||||
cardsToUpdate.entrySet().forEach(entry -> {
|
||||
for (Card card : entry.getValue().getCards(game)) {
|
||||
|
|
@ -373,8 +383,9 @@ public final class ZonesHandler {
|
|||
* that isn't a transforming double-faced card onto the battlefield transformed or converted, that card stays in
|
||||
* its current zone.
|
||||
*/
|
||||
// TODO: remove after tdfc rework
|
||||
boolean wantToTransform = Boolean.TRUE.equals(game.getState().getValue(TransformAbility.VALUE_KEY_ENTER_TRANSFORMED + card.getId()));
|
||||
if (wantToTransform) {
|
||||
if (wantToTransform && !(card instanceof DoubleFacedCardHalf)) {
|
||||
isGoodToMove = card.isTransformable() && card.getSecondCardFace().isPermanent(game);
|
||||
} else {
|
||||
isGoodToMove = card.isPermanent(game);
|
||||
|
|
@ -407,8 +418,7 @@ public final class ZonesHandler {
|
|||
} else if (card instanceof RoomCardHalf) {
|
||||
// Only the main room card can etb
|
||||
permanent = new PermanentCard(card.getMainCard(), event.getPlayerId(), game);
|
||||
}
|
||||
else if (card instanceof ModalDoubleFacedCard) {
|
||||
} else if (card instanceof DoubleFacedCard) {
|
||||
// main mdf card must be processed before that call (e.g. only halves can be moved to battlefield)
|
||||
throw new IllegalStateException("Unexpected trying of move mdf card to battlefield instead half");
|
||||
} else if (card instanceof Permanent) {
|
||||
|
|
@ -533,4 +543,4 @@ public final class ZonesHandler {
|
|||
return card;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ import java.util.stream.Collectors;
|
|||
public class Battlefield implements Serializable {
|
||||
|
||||
private final Map<UUID, Permanent> field = new LinkedHashMap<>();
|
||||
private final Map<UUID, Permanent> permanentsEntering = new LinkedHashMap<>();
|
||||
|
||||
public Battlefield() {
|
||||
}
|
||||
|
|
@ -26,6 +27,9 @@ public class Battlefield implements Serializable {
|
|||
for (Entry<UUID, Permanent> entry : battlefield.field.entrySet()) {
|
||||
field.put(entry.getKey(), entry.getValue().copy());
|
||||
}
|
||||
for (Entry<UUID, Permanent> entry : battlefield.permanentsEntering.entrySet()) {
|
||||
permanentsEntering.put(entry.getKey(), entry.getValue().copy());
|
||||
}
|
||||
}
|
||||
|
||||
public Battlefield copy() {
|
||||
|
|
@ -36,10 +40,14 @@ public class Battlefield implements Serializable {
|
|||
for (Permanent perm : field.values()) {
|
||||
perm.reset(game);
|
||||
}
|
||||
for (Permanent perm : permanentsEntering.values()) {
|
||||
perm.reset(game);
|
||||
}
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
field.clear();
|
||||
permanentsEntering.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -156,6 +164,11 @@ public class Battlefield implements Serializable {
|
|||
return field.containsKey(key);
|
||||
}
|
||||
|
||||
public Map<UUID, Permanent> getPermanentsEntering() {
|
||||
return permanentsEntering;
|
||||
}
|
||||
|
||||
|
||||
public void beginningOfTurn(Game game) {
|
||||
for (Permanent perm : field.values()) {
|
||||
perm.beginningOfTurn(game);
|
||||
|
|
|
|||
|
|
@ -479,6 +479,13 @@ public interface Permanent extends Card, Controllable {
|
|||
|
||||
boolean wasRoomUnlockedOnCast();
|
||||
|
||||
/**
|
||||
* used to reset the locked status of a room. Only used when copying a room
|
||||
* or creating a token copy of a room permanent. Could most likely be removed
|
||||
* after a designation class added.
|
||||
*/
|
||||
void resetLockedStatus();
|
||||
|
||||
boolean isLeftDoorUnlocked();
|
||||
|
||||
boolean isRightDoorUnlocked();
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
package mage.game.permanent;
|
||||
|
||||
import mage.MageObject;
|
||||
import mage.ObjectColor;
|
||||
import mage.abilities.Abilities;
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.common.RoomAbility;
|
||||
import mage.abilities.costs.mana.ManaCost;
|
||||
import mage.abilities.costs.mana.ManaCosts;
|
||||
import mage.abilities.keyword.NightboundAbility;
|
||||
|
|
@ -30,6 +32,8 @@ public class PermanentCard extends PermanentImpl {
|
|||
|
||||
protected int maxLevelCounters;
|
||||
protected int zoneChangeCounter;
|
||||
protected ObjectColor originalColor;
|
||||
protected ObjectColor originalFrameColor;
|
||||
|
||||
public PermanentCard(Card card, UUID controllerId, Game game) {
|
||||
super(card.getId(), card.getOwnerId(), controllerId, card.getName()); // card id
|
||||
|
|
@ -46,7 +50,7 @@ public class PermanentCard extends PermanentImpl {
|
|||
// if you use it in test code or for permanent's copy effects then call CardUtil.getDefaultCardSideForBattlefield for default side
|
||||
// it's a basic check and still allows to create permanent from instant or sorcery
|
||||
boolean goodForBattlefield = true;
|
||||
if (card instanceof ModalDoubleFacedCard) {
|
||||
if (card instanceof DoubleFacedCard) {
|
||||
goodForBattlefield = false;
|
||||
} else if (card instanceof SplitCard) {
|
||||
// fused spells allowed (it uses main card)
|
||||
|
|
@ -65,9 +69,24 @@ public class PermanentCard extends PermanentImpl {
|
|||
throw new IllegalArgumentException("Wrong code usage: can't create permanent card from split or mdf: " + card.getName());
|
||||
}
|
||||
|
||||
this.card = card;
|
||||
// if two permanent sides, set front and second side
|
||||
if (card instanceof DoubleFacedCardHalf && card.isPermanent() && ((DoubleFacedCardHalf) card).getOtherSide().isPermanent()) {
|
||||
if (((DoubleFacedCardHalf) card).isBackSide()) {
|
||||
secondSideCard = card;
|
||||
this.card = ((DoubleFacedCardHalf) card).getOtherSide().copy();
|
||||
this.transformed = true;
|
||||
init(secondSideCard, game);
|
||||
} else {
|
||||
secondSideCard = ((DoubleFacedCardHalf) card).getOtherSide().copy();
|
||||
this.card = card;
|
||||
init(card, game);
|
||||
}
|
||||
} else {
|
||||
this.card = card;
|
||||
init(card, game);
|
||||
}
|
||||
|
||||
this.zoneChangeCounter = card.getZoneChangeCounter(game); // local value already set to the raised number
|
||||
init(card, game);
|
||||
}
|
||||
|
||||
private void init(Card card, Game game) {
|
||||
|
|
@ -75,7 +94,7 @@ public class PermanentCard extends PermanentImpl {
|
|||
toughness = card.getToughness().copy();
|
||||
startingLoyalty = card.getStartingLoyalty();
|
||||
startingDefense = card.getStartingDefense();
|
||||
copyFromCard(card, game);
|
||||
copyFromCard(card, game, false);
|
||||
// if temporary added abilities to the spell/card exist, you need to add it to the permanent derived from that card
|
||||
Abilities<Ability> otherAbilities = game.getState().getAllOtherAbilities(card.getId());
|
||||
if (otherAbilities != null) {
|
||||
|
|
@ -86,10 +105,11 @@ public class PermanentCard extends PermanentImpl {
|
|||
}
|
||||
|
||||
// if transformed on ETB
|
||||
if (card.isTransformable()) {
|
||||
if (game.getState().getValue(TransformAbility.VALUE_KEY_ENTER_TRANSFORMED + getId()) != null
|
||||
// TODO: remove after tdfc rework
|
||||
if (card.isTransformable() && !(card instanceof DoubleFacedCardHalf)) {
|
||||
if (game.getState().getValue(TransformAbility.VALUE_KEY_ENTER_TRANSFORMED + card.getId()) != null
|
||||
|| NightboundAbility.checkCard(this, game)) {
|
||||
game.getState().setValue(TransformAbility.VALUE_KEY_ENTER_TRANSFORMED + getId(), null);
|
||||
game.getState().setValue(TransformAbility.VALUE_KEY_ENTER_TRANSFORMED + card.getId(), null);
|
||||
TransformAbility.transformPermanent(this, game, null);
|
||||
}
|
||||
}
|
||||
|
|
@ -100,19 +120,37 @@ public class PermanentCard extends PermanentImpl {
|
|||
this.card = permanent.card.copy();
|
||||
this.maxLevelCounters = permanent.maxLevelCounters;
|
||||
this.zoneChangeCounter = permanent.zoneChangeCounter;
|
||||
this.originalColor = permanent.originalColor.copy();
|
||||
this.originalFrameColor = permanent.originalFrameColor.copy();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset(Game game) {
|
||||
// when the permanent is reset, copy all original values from the card
|
||||
// must copy card each reset so that the original values don't get modified
|
||||
copyFromCard(card, game);
|
||||
if (transformed && secondSideCard != null && getCard() instanceof DoubleFacedCardHalf) {
|
||||
copyFromCard(secondSideCard, game, true);
|
||||
} else {
|
||||
copyFromCard(card, game, true);
|
||||
}
|
||||
power.resetToBaseValue();
|
||||
toughness.resetToBaseValue();
|
||||
super.reset(game);
|
||||
}
|
||||
|
||||
protected void copyFromCard(final Card card, final Game game) {
|
||||
@Override
|
||||
protected void initOtherFace(Game game) {
|
||||
if (!(secondSideCard instanceof DoubleFacedCardHalf)) {
|
||||
return;
|
||||
}
|
||||
if (transformed) {
|
||||
copyFromCard(secondSideCard, game, false);
|
||||
} else {
|
||||
copyFromCard(card, game, false);
|
||||
}
|
||||
}
|
||||
|
||||
protected void copyFromCard(final Card card, final Game game, boolean isReset) {
|
||||
// TODO: must research - is it copy all fields or something miss
|
||||
this.name = card.getName();
|
||||
this.abilities.clear();
|
||||
|
|
@ -122,6 +160,11 @@ public class PermanentCard extends PermanentImpl {
|
|||
this.abilities.add(ability.copy());
|
||||
}
|
||||
}
|
||||
} else if (card.getId() != this.getId()) {
|
||||
// if different id, abilities need to be added to game state for continuous/triggers
|
||||
for (Ability ability : card.getAbilities()) {
|
||||
this.addAbility(ability, card.getId(), game, true);
|
||||
}
|
||||
} else {
|
||||
// copy only own abilities; all dynamic added abilities must be added in the parent call
|
||||
this.abilities = card.getAbilities().copy();
|
||||
|
|
@ -131,13 +174,23 @@ public class PermanentCard extends PermanentImpl {
|
|||
this.abilities.setSourceId(objectId);
|
||||
this.cardType.clear();
|
||||
this.cardType.addAll(card.getCardType());
|
||||
this.color = card.getColor(game).copy();
|
||||
this.frameColor = card.getFrameColor(game).copy();
|
||||
if (!isReset) {
|
||||
// save color from game state on first creation
|
||||
this.color = card.getColor(game).copy();
|
||||
this.frameColor = card.getFrameColor(game).copy();
|
||||
this.originalColor = card.getColor(game).copy();
|
||||
this.originalFrameColor = card.getFrameColor(game).copy();
|
||||
} else {
|
||||
this.color = originalColor.copy();
|
||||
this.frameColor = originalFrameColor.copy();
|
||||
}
|
||||
this.frameStyle = card.getFrameStyle();
|
||||
this.manaCost = card.getManaCost().copy();
|
||||
if (card instanceof PermanentCard) {
|
||||
this.maxLevelCounters = ((PermanentCard) card).maxLevelCounters;
|
||||
}
|
||||
this.power = card.getPower().copy();
|
||||
this.toughness = card.getToughness().copy();
|
||||
this.subtype.copyFrom(card.getSubtype());
|
||||
this.supertype.clear();
|
||||
this.supertype.addAll(card.getSuperType());
|
||||
|
|
@ -149,7 +202,7 @@ public class PermanentCard extends PermanentImpl {
|
|||
this.setImageFileName(card.getImageFileName());
|
||||
this.setImageNumber(card.getImageNumber());
|
||||
|
||||
if (card.getSecondCardFace() != null) {
|
||||
if (card.getSecondCardFace() != null && !(card instanceof DoubleFacedCardHalf)) {
|
||||
this.secondSideCardClazz = card.getSecondCardFace().getClass();
|
||||
}
|
||||
if (card.getMeldsToCard() != null) {
|
||||
|
|
@ -158,6 +211,22 @@ public class PermanentCard extends PermanentImpl {
|
|||
this.nightCard = card.isNightCard();
|
||||
this.flipCard = card.isFlipCard();
|
||||
this.flipCardName = card.getFlipCardName();
|
||||
// Rooms set characteristics at the end so nothing gets overwritten
|
||||
if (card instanceof RoomCard) {
|
||||
RoomCard.setRoomCharacteristics(this, game);
|
||||
if (!isReset) {
|
||||
RoomAbility roomAbility = null;
|
||||
for (Ability ability : this.abilities) {
|
||||
if (ability instanceof RoomAbility) {
|
||||
roomAbility = (RoomAbility) ability;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (roomAbility != null) {
|
||||
roomAbility.applyCharacteristics(game, this);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ import mage.abilities.hint.HintUtils;
|
|||
import mage.abilities.keyword.*;
|
||||
import mage.cards.Card;
|
||||
import mage.cards.CardImpl;
|
||||
import mage.abilities.common.RoomAbility;
|
||||
import mage.constants.*;
|
||||
import mage.counters.Counter;
|
||||
import mage.counters.CounterType;
|
||||
|
|
@ -706,12 +707,15 @@ public abstract class PermanentImpl extends CardImpl implements Permanent {
|
|||
+ CardUtil.getSourceLogName(game, source, this.getId()));
|
||||
this.setTransformed(!this.transformed);
|
||||
this.transformCount++;
|
||||
initOtherFace(game);
|
||||
game.applyEffects(); // not process action - no firing of simultaneous events yet
|
||||
this.replaceEvent(EventType.TRANSFORMING, game);
|
||||
game.addSimultaneousEvent(GameEvent.getEvent(EventType.TRANSFORMED, this.getId(), this.getControllerId()));
|
||||
return true;
|
||||
}
|
||||
|
||||
protected abstract void initOtherFace(Game game);
|
||||
|
||||
@Override
|
||||
public int getTransformCount() {
|
||||
return transformCount;
|
||||
|
|
@ -2099,6 +2103,12 @@ public abstract class PermanentImpl extends CardImpl implements Permanent {
|
|||
return roomWasUnlockedOnCast;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resetLockedStatus() {
|
||||
leftHalfUnlocked = false;
|
||||
rightHalfUnlocked = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLeftDoorUnlocked() {
|
||||
return leftHalfUnlocked;
|
||||
|
|
@ -2141,15 +2151,27 @@ public abstract class PermanentImpl extends CardImpl implements Permanent {
|
|||
rightHalfUnlocked = true;
|
||||
}
|
||||
|
||||
// Fire door unlock event
|
||||
// Update intrinsic stats/abilities from unlocking
|
||||
// find the RoomCharacteristicsEffect applied by this permanent's ability
|
||||
Abilities<Ability> abilities = this.getAbilities(game);
|
||||
for (Ability ability : abilities) {
|
||||
if (ability instanceof RoomAbility) {
|
||||
((RoomAbility) ability).restoreUnlockedStats(game, this);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Create door unlock event
|
||||
GameEvent event = new GameEvent(GameEvent.EventType.DOOR_UNLOCKED, getId(), source, source.getControllerId());
|
||||
event.setFlag(isLeftDoor);
|
||||
game.fireEvent(event);
|
||||
|
||||
// Check if room is now fully unlocked
|
||||
boolean otherDoorUnlocked = isLeftDoor ? rightHalfUnlocked : leftHalfUnlocked;
|
||||
if (otherDoorUnlocked) {
|
||||
game.fireEvent(new GameEvent(EventType.ROOM_FULLY_UNLOCKED, getId(), source, source.getControllerId()));
|
||||
game.addSimultaneousEvent(event);
|
||||
game.addSimultaneousEvent(new GameEvent(EventType.ROOM_FULLY_UNLOCKED, getId(), source, source.getControllerId()));
|
||||
} else {
|
||||
game.fireEvent(event);
|
||||
}
|
||||
|
||||
return true;
|
||||
|
|
|
|||
|
|
@ -5,8 +5,8 @@ import mage.MageObject;
|
|||
import mage.abilities.Ability;
|
||||
import mage.abilities.costs.mana.ManaCost;
|
||||
import mage.abilities.keyword.ChangelingAbility;
|
||||
import mage.abilities.keyword.TransformAbility;
|
||||
import mage.cards.Card;
|
||||
import mage.cards.RoomCard;
|
||||
import mage.constants.EmptyNames;
|
||||
import mage.game.Game;
|
||||
import mage.game.events.ZoneChangeEvent;
|
||||
|
|
@ -30,13 +30,13 @@ public class PermanentToken extends PermanentImpl {
|
|||
this.token = token.copy();
|
||||
this.token.getAbilities().newOriginalId(); // neccessary if token has ability like DevourAbility()
|
||||
this.token.getAbilities().setSourceId(objectId);
|
||||
this.power = new MageInt(token.getPower().getModifiedBaseValue());
|
||||
this.toughness = new MageInt(token.getToughness().getModifiedBaseValue());
|
||||
this.copyFromToken(this.token, game, false); // needed to have at this time (e.g. for subtypes for entersTheBattlefield replacement effects)
|
||||
|
||||
// if transformed on ETB
|
||||
if (this.token.isEntersTransformed()) {
|
||||
TransformAbility.transformPermanent(this, game, null);
|
||||
this.setTransformed(true);
|
||||
this.copyFromToken(this.token.getBackFace(), game, false);
|
||||
} else {
|
||||
this.copyFromToken(this.token, game, false); // needed to have at this time (e.g. for subtypes for entersTheBattlefield replacement effects)
|
||||
}
|
||||
|
||||
// token's ZCC must be synced with original token to keep abilities settings
|
||||
|
|
@ -53,7 +53,11 @@ public class PermanentToken extends PermanentImpl {
|
|||
|
||||
@Override
|
||||
public void reset(Game game) {
|
||||
copyFromToken(token, game, true);
|
||||
if (this.isTransformed()) {
|
||||
copyFromToken(token.getBackFace(), game, true);
|
||||
} else {
|
||||
copyFromToken(token, game, true);
|
||||
}
|
||||
super.reset(game);
|
||||
// Because the P/T objects have there own base value for reset we have to take it from there instead of from the basic token object
|
||||
this.power.resetToBaseValue();
|
||||
|
|
@ -110,8 +114,12 @@ public class PermanentToken extends PermanentImpl {
|
|||
if (this.abilities.containsClass(ChangelingAbility.class)) {
|
||||
this.subtype.setIsAllCreatureTypes(true);
|
||||
}
|
||||
|
||||
this.power = new MageInt(token.getPower().getModifiedBaseValue());
|
||||
this.toughness = new MageInt(token.getToughness().getModifiedBaseValue());
|
||||
CardUtil.copySetAndCardNumber(this, token);
|
||||
if (token.getCopySourceCard() instanceof RoomCard) {
|
||||
RoomCard.setRoomCharacteristics(this, game);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -161,4 +169,13 @@ public class PermanentToken extends PermanentImpl {
|
|||
public MageObject getOtherFace() {
|
||||
return this.transformed ? token : this.token.getBackFace();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initOtherFace(Game game) {
|
||||
if (transformed) {
|
||||
copyFromToken(token.getBackFace(), game, false);
|
||||
} else {
|
||||
copyFromToken(token, game, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package mage.game.stack;
|
|||
|
||||
import mage.*;
|
||||
import mage.abilities.*;
|
||||
import mage.abilities.common.SpellTransformedAbility;
|
||||
import mage.abilities.costs.mana.ActivationManaAbilityStep;
|
||||
import mage.abilities.costs.mana.ManaCost;
|
||||
import mage.abilities.costs.mana.ManaCosts;
|
||||
|
|
@ -102,6 +103,11 @@ public class Spell extends StackObjectImpl implements Card {
|
|||
this.ability = ability;
|
||||
this.ability.setControllerId(controllerId);
|
||||
|
||||
// 712.8c TDFC spell "Its mana value is calculated using the mana cost of its front face"
|
||||
if(ability instanceof SpellTransformedAbility && manaCost.isEmpty()) {
|
||||
this.manaCost = card.getMainCard().getManaCost().copy();
|
||||
this.ability.setSourceId(affectedCard.getId()); // Maybe wrong? Permanent has incorrect id otherwise
|
||||
}
|
||||
if (ability.getSpellAbilityCastMode().isFaceDown()) {
|
||||
// TODO: need research:
|
||||
// - why it use game param for color and subtype (possible bug?)
|
||||
|
|
@ -1187,6 +1193,16 @@ public class Spell extends StackObjectImpl implements Card {
|
|||
throw new UnsupportedOperationException("Not supported.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPT(int power, int toughness) {
|
||||
throw new UnsupportedOperationException("Not supported.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPT(MageInt power, MageInt toughness) {
|
||||
throw new UnsupportedOperationException("Not supported.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean cantBeAttachedBy(MageObject attachment, Ability source, Game game, boolean silentMode) {
|
||||
throw new UnsupportedOperationException("Not supported.");
|
||||
|
|
|
|||
|
|
@ -4106,7 +4106,11 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
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 CardWithSpellOption) {
|
||||
} else if (object instanceof TransformingDoubleFacedCard) {
|
||||
TransformingDoubleFacedCard mainCard = (TransformingDoubleFacedCard) object;
|
||||
getPlayableFromObjectSingle(game, fromZone, mainCard.getLeftHalfCard(), mainCard.getLeftHalfCard().getAbilities(game), availableMana, output);
|
||||
getPlayableFromObjectSingle(game, fromZone, mainCard, mainCard.getSharedAbilities(game), availableMana, output);
|
||||
} else if (object instanceof CardWithSpellOption) {
|
||||
// adventure must use different card characteristics for different spells (main or adventure)
|
||||
CardWithSpellOption cardWithSpellOption = (CardWithSpellOption) object;
|
||||
getPlayableFromObjectSingle(game, fromZone, cardWithSpellOption.getSpellCard(), cardWithSpellOption.getSpellCard().getAbilities(game), availableMana, output);
|
||||
|
|
@ -4258,6 +4262,12 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
boolean isPlaySpell = (ability instanceof SpellAbility);
|
||||
boolean isPlayLand = (ability instanceof PlayLandAbility);
|
||||
|
||||
// ignore backside of TDFC
|
||||
// TODO: maybe better way to ignore
|
||||
if (isPlaySpell && ((SpellAbility) ability).getSpellAbilityType() == SpellAbilityType.TRANSFORMED_RIGHT) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// play land restrictions
|
||||
if (isPlayLand && game.getContinuousEffects().preventedByRuleModification(
|
||||
GameEvent.getEvent(GameEvent.EventType.PLAY_LAND, ability.getSourceId(),
|
||||
|
|
@ -4946,8 +4956,9 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
// or "converted," it enters the battlefield with its back face up. If a player is instructed to put a card
|
||||
// that isn't a transforming double-faced card onto the battlefield transformed or converted, that card stays in
|
||||
// its current zone.
|
||||
// TODO: can probably remove/change after tdfc rework, should only be sending transformed side
|
||||
Boolean enterTransformed = (Boolean) game.getState().getValue(TransformAbility.VALUE_KEY_ENTER_TRANSFORMED + card.getId());
|
||||
if (enterTransformed != null && enterTransformed && !card.isTransformable()) {
|
||||
if (enterTransformed != null && enterTransformed && !card.isTransformable() && !(card instanceof TransformingDoubleFacedCardHalf)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1261,8 +1261,8 @@ public final class CardUtil {
|
|||
permCard = card;
|
||||
} else if (card instanceof CardWithSpellOption) {
|
||||
permCard = card;
|
||||
} else if (card instanceof ModalDoubleFacedCard) {
|
||||
permCard = ((ModalDoubleFacedCard) card).getLeftHalfCard();
|
||||
} else if (card instanceof DoubleFacedCard) {
|
||||
permCard = ((DoubleFacedCard) card).getLeftHalfCard();
|
||||
} else {
|
||||
permCard = card;
|
||||
}
|
||||
|
|
@ -1294,8 +1294,8 @@ public final class CardUtil {
|
|||
// 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 ModalDoubleFacedCard) {
|
||||
return ((ModalDoubleFacedCard) card).getLeftHalfCard().getName();
|
||||
} else if (card instanceof DoubleFacedCard) {
|
||||
return ((DoubleFacedCard) card).getLeftHalfCard().getName();
|
||||
} else {
|
||||
return card.getName();
|
||||
}
|
||||
|
|
@ -1669,6 +1669,22 @@ public final class CardUtil {
|
|||
game.getState().setValue("PlayFromNotOwnHandZone" + rightHalfCard.getId(), Boolean.TRUE);
|
||||
}
|
||||
|
||||
// handle TDFC
|
||||
if (card instanceof TransformingDoubleFacedCard) {
|
||||
TransformingDoubleFacedCardHalf frontFace = ((TransformingDoubleFacedCard) card).getLeftHalfCard();
|
||||
TransformingDoubleFacedCardHalf backFace = ((TransformingDoubleFacedCard) card).getRightHalfCard();
|
||||
|
||||
if (manaCost != null) {
|
||||
// get additional cost if any
|
||||
Costs<Cost> additionalCostsMDFCLeft = frontFace.getSpellAbility().getCosts();
|
||||
// set alternative cost and any additional cost
|
||||
player.setCastSourceIdWithAlternateMana(frontFace.getId(), manaCost, additionalCostsMDFCLeft, MageIdentifier.Default);
|
||||
}
|
||||
|
||||
// allow just the front face
|
||||
game.getState().setValue("PlayFromNotOwnHandZone" + frontFace.getId(), Boolean.TRUE);
|
||||
}
|
||||
|
||||
// handle adventure cards
|
||||
if (card instanceof CardWithSpellOption) {
|
||||
Card creatureCard = card.getMainCard();
|
||||
|
|
@ -1706,9 +1722,9 @@ public final class CardUtil {
|
|||
game.getState().setValue("PlayFromNotOwnHandZone" + leftHalfCard.getId(), null);
|
||||
game.getState().setValue("PlayFromNotOwnHandZone" + rightHalfCard.getId(), null);
|
||||
}
|
||||
if (card instanceof ModalDoubleFacedCard) {
|
||||
ModalDoubleFacedCardHalf leftHalfCard = ((ModalDoubleFacedCard) card).getLeftHalfCard();
|
||||
ModalDoubleFacedCardHalf rightHalfCard = ((ModalDoubleFacedCard) card).getRightHalfCard();
|
||||
if (card instanceof DoubleFacedCard) {
|
||||
DoubleFacedCardHalf leftHalfCard = ((DoubleFacedCard) card).getLeftHalfCard();
|
||||
DoubleFacedCardHalf rightHalfCard = ((DoubleFacedCard) card).getRightHalfCard();
|
||||
game.getState().setValue("PlayFromNotOwnHandZone" + leftHalfCard.getId(), null);
|
||||
game.getState().setValue("PlayFromNotOwnHandZone" + rightHalfCard.getId(), null);
|
||||
}
|
||||
|
|
@ -2100,8 +2116,8 @@ public final class CardUtil {
|
|||
res.add(mainCard);
|
||||
res.add(mainCard.getLeftHalfCard());
|
||||
res.add(mainCard.getRightHalfCard());
|
||||
} else if (object instanceof ModalDoubleFacedCard || object instanceof ModalDoubleFacedCardHalf) {
|
||||
ModalDoubleFacedCard mainCard = (ModalDoubleFacedCard) ((Card) object).getMainCard();
|
||||
} else if (object instanceof DoubleFacedCard || object instanceof DoubleFacedCardHalf) {
|
||||
DoubleFacedCard mainCard = (DoubleFacedCard) ((Card) object).getMainCard();
|
||||
res.add(mainCard);
|
||||
res.add(mainCard.getLeftHalfCard());
|
||||
res.add(mainCard.getRightHalfCard());
|
||||
|
|
|
|||
|
|
@ -643,8 +643,8 @@ public final class ManaUtil {
|
|||
secondSide = ((SplitCard) card).getRightHalfCard();
|
||||
} else if (card instanceof CardWithSpellOption) {
|
||||
secondSide = ((CardWithSpellOption) card).getSpellCard();
|
||||
} else if (card instanceof ModalDoubleFacedCard) {
|
||||
secondSide = ((ModalDoubleFacedCard) card).getRightHalfCard();
|
||||
} else if (card instanceof DoubleFacedCard) {
|
||||
secondSide = ((DoubleFacedCard) card).getRightHalfCard();
|
||||
} else {
|
||||
secondSide = card.getSecondCardFace();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import mage.abilities.Abilities;
|
|||
import mage.abilities.Ability;
|
||||
import mage.abilities.effects.common.continuous.BecomesFaceDownCreatureEffect;
|
||||
import mage.abilities.keyword.PrototypeAbility;
|
||||
import mage.cards.Card;
|
||||
import mage.cards.*;
|
||||
import mage.constants.CardType;
|
||||
import mage.constants.SuperType;
|
||||
import mage.game.Game;
|
||||
|
|
@ -91,9 +91,13 @@ public class CopyTokenFunction {
|
|||
copyToToken(target, sourceObj, game);
|
||||
CardUtil.copySetAndCardNumber(target, sourceObj);
|
||||
// second side
|
||||
if (sourceObj.isTransformable()) {
|
||||
if (sourceObj.isTransformable() && !(sourceObj instanceof DoubleFacedCardHalf)) {
|
||||
copyToToken(target.getBackFace(), sourceObj.getSecondCardFace(), game);
|
||||
CardUtil.copySetAndCardNumber(target.getBackFace(), sourceObj.getSecondCardFace());
|
||||
} else if (sourceObj.isTransformable() && sourceObj instanceof DoubleFacedCardHalf) {
|
||||
// double faced card
|
||||
copyToToken(target.getBackFace(), ((DoubleFacedCardHalf) sourceObj).getOtherSide(), game);
|
||||
CardUtil.copySetAndCardNumber(target.getBackFace(), ((DoubleFacedCardHalf) sourceObj).getOtherSide());
|
||||
}
|
||||
|
||||
// apply prototyped status
|
||||
|
|
@ -108,6 +112,35 @@ public class CopyTokenFunction {
|
|||
return;
|
||||
}
|
||||
|
||||
// from double faced card spell
|
||||
if (source instanceof DoubleFacedCardHalf) {
|
||||
DoubleFacedCardHalf sourceCard = (DoubleFacedCardHalf) source;
|
||||
Card frontSide;
|
||||
Card backSide = null;
|
||||
if (sourceCard.isTransformable()) {
|
||||
if (sourceCard.isBackSide()) {
|
||||
target.setEntersTransformed(true);
|
||||
frontSide = sourceCard.getOtherSide();
|
||||
backSide = sourceCard;
|
||||
} else {
|
||||
frontSide = sourceCard;
|
||||
backSide = sourceCard.getOtherSide();
|
||||
}
|
||||
} else {
|
||||
frontSide = sourceCard;
|
||||
}
|
||||
// main side
|
||||
copyToToken(target, frontSide, game);
|
||||
target.setCopySourceCard(sourceCard);
|
||||
CardUtil.copySetAndCardNumber(target, frontSide);
|
||||
// second side
|
||||
if (backSide != null) {
|
||||
copyToToken(target.getBackFace(), backSide, game);
|
||||
CardUtil.copySetAndCardNumber(target, backSide);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// from another card (example: Embalm ability)
|
||||
Card sourceObj = CardUtil.getDefaultCardSideForBattlefield(game, source.getMainCard());
|
||||
target.setCopySourceCard(sourceObj);
|
||||
|
|
@ -121,8 +154,14 @@ public class CopyTokenFunction {
|
|||
// must create back face??
|
||||
throw new IllegalStateException("Wrong code usage: back face must be non null: " + target.getName() + " - " + target.getClass().getSimpleName());
|
||||
}
|
||||
copyToToken(target.getBackFace(), source.getSecondCardFace(), game);
|
||||
CardUtil.copySetAndCardNumber(target.getBackFace(), source.getSecondCardFace());
|
||||
Card secondFace;
|
||||
if (source instanceof DoubleFacedCard) {
|
||||
secondFace = ((DoubleFacedCard) source).getRightHalfCard();
|
||||
} else {
|
||||
secondFace = source.getSecondCardFace();
|
||||
}
|
||||
copyToToken(target.getBackFace(), secondFace, game);
|
||||
CardUtil.copySetAndCardNumber(target.getBackFace(), secondFace);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue