diff --git a/Mage/src/main/java/mage/cards/Card.java b/Mage/src/main/java/mage/cards/Card.java index 21776e6be3a..8882c14869d 100644 --- a/Mage/src/main/java/mage/cards/Card.java +++ b/Mage/src/main/java/mage/cards/Card.java @@ -250,6 +250,16 @@ public interface Card extends MageObject, Ownerable { List getAttachments(); + /** + * @param attachment can be any object: card, permanent, token + * @param source can be null for default checks like state base + * @param game + * @param silentMode - use it to ignore warning message for users (e.g. for + * checking only) + * @return + */ + boolean cantBeAttachedBy(MageObject attachment, Ability source, Game game, boolean silentMode); + boolean addAttachment(UUID permanentId, Ability source, Game game); boolean removeAttachment(UUID permanentId, Ability source, Game game); diff --git a/Mage/src/main/java/mage/cards/CardImpl.java b/Mage/src/main/java/mage/cards/CardImpl.java index 27d65fe059f..2edbf43c918 100644 --- a/Mage/src/main/java/mage/cards/CardImpl.java +++ b/Mage/src/main/java/mage/cards/CardImpl.java @@ -7,10 +7,7 @@ import mage.abilities.*; import mage.abilities.common.EntersBattlefieldTriggeredAbility; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.effects.common.continuous.HasSubtypesSourceEffect; -import mage.abilities.keyword.ChangelingAbility; -import mage.abilities.keyword.FlashbackAbility; -import mage.abilities.keyword.ReconfigureAbility; -import mage.abilities.keyword.SunburstAbility; +import mage.abilities.keyword.*; import mage.abilities.mana.ActivatedManaAbilityImpl; import mage.cards.mock.MockableCard; import mage.cards.repository.PluginClassloaderRegistery; @@ -919,6 +916,32 @@ public abstract class CardImpl extends MageObjectImpl implements Card { return attachments; } + @Override + public boolean cantBeAttachedBy(MageObject attachment, Ability source, Game game, boolean silentMode) { + for (ProtectionAbility ability : this.getAbilities(game).getProtectionAbilities()) { + if ((!attachment.hasSubtype(SubType.AURA, game) || ability.removesAuras()) + && (!attachment.hasSubtype(SubType.EQUIPMENT, game) || ability.removesEquipment()) + && !attachment.getId().equals(ability.getAuraIdNotToBeRemoved()) + && !ability.canTarget(attachment, game)) { + return !ability.getDoesntRemoveControlled() || Objects.equals(getControllerOrOwnerId(), game.getControllerId(attachment.getId())); + } + } + + boolean canAttach = true; + Permanent attachmentPermanent = game.getPermanent(attachment.getId()); + // If attachment is an aura, ensures this permanent can still be legally enchanted, according to the enchantment's Enchant ability + if (attachment.hasSubtype(SubType.AURA, game) + && attachmentPermanent != null + && attachmentPermanent.getSpellAbility() != null + && !attachmentPermanent.getSpellAbility().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 = attachmentPermanent.getSpellAbility().getTargets().get(0).copy().withNotTarget(true).stillLegalTarget(attachmentPermanent.getControllerId(), this.getId(), source, game); + } + + return !canAttach || game.getContinuousEffects().preventedByRuleModification(new StayAttachedEvent(this.getId(), attachment.getId(), source), null, game, silentMode); + } + @Override public boolean addAttachment(UUID permanentId, Ability source, Game game) { if (permanentId == null @@ -942,6 +965,9 @@ public abstract class CardImpl extends MageObjectImpl implements Card { && (attachment.isCreature(game) || !this.isLand(game))) { return false; } + if (this.cantBeAttachedBy(attachment, source, game, false)) { + return false; + } if (game.replaceEvent(new AttachEvent(objectId, attachment, source))) { return false; } diff --git a/Mage/src/main/java/mage/game/permanent/Permanent.java b/Mage/src/main/java/mage/game/permanent/Permanent.java index 5df20522f2f..1447879b362 100644 --- a/Mage/src/main/java/mage/game/permanent/Permanent.java +++ b/Mage/src/main/java/mage/game/permanent/Permanent.java @@ -132,16 +132,6 @@ public interface Permanent extends Card, Controllable { boolean hasProtectionFrom(MageObject source, Game game); - /** - * @param attachment can be any object: card, permanent, token - * @param source can be null for default checks like state base - * @param game - * @param silentMode - use it to ignore warning message for users (e.g. for - * checking only) - * @return - */ - boolean cantBeAttachedBy(MageObject attachment, Ability source, Game game, boolean silentMode); - boolean wasControlledFromStartOfControllerTurn(); boolean hasSummoningSickness(); diff --git a/Mage/src/main/java/mage/game/permanent/PermanentImpl.java b/Mage/src/main/java/mage/game/permanent/PermanentImpl.java index 409ace36912..d2af86f3f39 100644 --- a/Mage/src/main/java/mage/game/permanent/PermanentImpl.java +++ b/Mage/src/main/java/mage/game/permanent/PermanentImpl.java @@ -1363,32 +1363,6 @@ public abstract class PermanentImpl extends CardImpl implements Permanent { return false; } - @Override - public boolean cantBeAttachedBy(MageObject attachment, Ability source, Game game, boolean silentMode) { - for (ProtectionAbility ability : this.getAbilities(game).getProtectionAbilities()) { - if ((!attachment.hasSubtype(SubType.AURA, game) || ability.removesAuras()) - && (!attachment.hasSubtype(SubType.EQUIPMENT, game) || ability.removesEquipment()) - && !attachment.getId().equals(ability.getAuraIdNotToBeRemoved()) - && !ability.canTarget(attachment, game)) { - return !ability.getDoesntRemoveControlled() || isControlledBy(game.getControllerId(attachment.getId())); - } - } - - boolean canAttach = true; - Permanent attachmentPermanent = game.getPermanent(attachment.getId()); - // If attachment is an aura, ensures this permanent can still be legally enchanted, according to the enchantment's Enchant ability - if (attachment.hasSubtype(SubType.AURA, game) - && attachmentPermanent != null - && attachmentPermanent.getSpellAbility() != null - && !attachmentPermanent.getSpellAbility().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 = attachmentPermanent.getSpellAbility().getTargets().get(0).copy().withNotTarget(true).stillLegalTarget(attachmentPermanent.getControllerId(), this.getId(), source, game); - } - - return !canAttach || game.getContinuousEffects().preventedByRuleModification(new StayAttachedEvent(this.getId(), attachment.getId(), source), null, game, silentMode); - } - @Override public boolean destroy(Ability source, Game game) { return destroy(source, game, false); diff --git a/Mage/src/main/java/mage/game/stack/Spell.java b/Mage/src/main/java/mage/game/stack/Spell.java index a4939295c25..89c9b0e4e98 100644 --- a/Mage/src/main/java/mage/game/stack/Spell.java +++ b/Mage/src/main/java/mage/game/stack/Spell.java @@ -1204,6 +1204,11 @@ public class Spell extends StackObjectImpl implements Card { throw new UnsupportedOperationException("Not supported."); } + @Override + public boolean cantBeAttachedBy(MageObject attachment, Ability source, Game game, boolean silentMode) { + throw new UnsupportedOperationException("Not supported."); + } + @Override public boolean addAttachment(UUID permanentId, Ability source, Game game) { throw new UnsupportedOperationException("Not supported.");