Bestow rework/fixes (#13973)

* New Bestow test, minor improvements

* Partially rework Bestow to not rely on perpetual card modifications

* Add Bestow subtype tests, improve PrototypeTest

* Fix Subtype existing without required card type

* Improve docs, improve aura spell copy target copying check, improve subtype handling

* Add additional test

* Review improvements

* Remove subtype/type check

* Consolidate temporary becomeAura into function

* Add Enchant Creature ability
This commit is contained in:
ssk97 2025-09-27 23:57:31 -07:00 committed by GitHub
parent 95f53fc671
commit 8cd1bec19d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 248 additions and 166 deletions

View file

@ -20,7 +20,6 @@ import mage.game.MageObjectAttribute;
import mage.game.events.CopiedStackObjectEvent;
import mage.game.events.ZoneChangeEvent;
import mage.game.permanent.Permanent;
import mage.game.permanent.PermanentCard;
import mage.game.permanent.token.Token;
import mage.players.Player;
import mage.util.CardUtil;
@ -335,45 +334,39 @@ public class Spell extends StackObjectImpl implements Card {
counter(null, /*this.getSpellAbility()*/ game);
return false;
} else if (this.isEnchantment(game) && this.hasSubtype(SubType.AURA, game)) {
boolean bestow = SpellAbilityCastMode.BESTOW.equals(ability.getSpellAbilityCastMode());
if (ability.getTargets().stillLegal(ability, game)) {
boolean bestow = SpellAbilityCastMode.BESTOW.equals(ability.getSpellAbilityCastMode());
if (bestow) {
// before put to play:
// Must be removed first time, after that will be removed by continous effect
// Otherwise effects like evolve trigger from creature comes into play event
card.removeCardType(CardType.CREATURE);
card.addSubType(game, SubType.AURA);
BestowAbility.becomeAura(game, card);
}
UUID permId;
boolean flag;
boolean permanentCreated;
if (isCopy()) {
Token token = CopyTokenFunction.createTokenCopy(card, game, this);
// The token that a resolving copy of a spell becomes isnt said to have been created. (2020-09-25)
if (token.putOntoBattlefield(1, game, ability, getControllerId(), false, false, null, null, false)) {
permId = token.getLastAddedTokenIds().stream().findFirst().orElse(null);
flag = true;
permanentCreated = true;
} else {
permId = null;
flag = false;
permanentCreated = false;
}
} else {
permId = card.getId();
MageObjectReference mor = new MageObjectReference(getSpellAbility());
game.storePermanentCostsTags(mor, getSpellAbility());
flag = controller.moveCards(card, Zone.BATTLEFIELD, ability, game, false, faceDown, false, null);
permanentCreated = controller.moveCards(card, Zone.BATTLEFIELD, ability, game, false, faceDown, false, null);
}
if (flag) {
if (permanentCreated) {
if (bestow) {
// card will be copied during putOntoBattlefield, so the card of CardPermanent has to be changed
// TODO: Find a better way to prevent bestow creatures from being effected by creature affecting abilities
Permanent permanent = game.getPermanent(permId);
if (permanent instanceof PermanentCard) {
// after put to play:
// restore removed stats (see "before put to play" above)
permanent.setSpellAbility(ability); // otherwise spell ability without bestow will be set
card.addCardType(CardType.CREATURE);
card.getSubtype().remove(SubType.AURA);
}
permanent.setSpellAbility(ability); // otherwise spell ability without bestow will be set
// The continuous effect that makes the permanent an aura doesn't apply until after the permanent has already entered,
// so it must be modified manually here first. Same root cause as the Blood Moon problem https://github.com/magefree/mage/issues/4202
BestowAbility.becomeAura(game, permanent);
}
if (isCopy()) {
Permanent token = game.getPermanent(permId);
@ -381,7 +374,8 @@ public class Spell extends StackObjectImpl implements Card {
return false;
}
for (Ability ability2 : token.getAbilities()) {
if (!bestow || ability2 instanceof BestowAbility) {
if (ability2 instanceof SpellAbility && ability2.getTargets().size() == 1) {
// Copy aura SpellAbility's targets into the new token's SpellAbility
ability2.getTargets().get(0).add(ability.getFirstTarget(), game);
ability2.getEffects().get(0).apply(game, ability2);
return ability2.resolve(game);
@ -391,24 +385,13 @@ public class Spell extends StackObjectImpl implements Card {
}
return ability.resolve(game);
}
if (bestow) {
card.addCardType(game, CardType.CREATURE);
}
return false;
}
// Aura has no legal target and its a bestow enchantment -> Add it to battlefield as creature
if (SpellAbilityCastMode.BESTOW.equals(this.getSpellAbility().getSpellAbilityCastMode())) {
if (bestow) {
MageObjectReference mor = new MageObjectReference(getSpellAbility());
game.storePermanentCostsTags(mor, getSpellAbility());
if (controller.moveCards(card, Zone.BATTLEFIELD, ability, game, false, faceDown, false, null)) {
Permanent permanent = game.getPermanent(card.getId());
if (permanent instanceof PermanentCard) {
((PermanentCard) permanent).getCard().addCardType(game, CardType.CREATURE);
((PermanentCard) permanent).getCard().removeSubType(game, SubType.AURA);
return true;
}
}
return false;
return controller.moveCards(card, Zone.BATTLEFIELD, ability, game, false, faceDown, false, null);
} else {
//20091005 - 608.2b
if (!game.isSimulation()) {
@ -585,10 +568,8 @@ public class Spell extends StackObjectImpl implements Card {
return cardTypes;
}
if (SpellAbilityCastMode.BESTOW.equals(this.getSpellAbility().getSpellAbilityCastMode())) {
List<CardType> cardTypes = new ArrayList<>();
cardTypes.addAll(card.getCardType(game));
cardTypes.remove(CardType.CREATURE);
return cardTypes;
Card modifiedCard = this.getSpellAbility().getSpellAbilityCastMode().getTypeModifiedCardObjectCopy(card, this.getSpellAbility(), game);
return modifiedCard.getCardType(game);
}
return card.getCardType(game);
}
@ -600,26 +581,19 @@ public class Spell extends StackObjectImpl implements Card {
@Override
public SubTypes getSubtype(Game game) {
// Bestow's changes are non-copiable, and must be reapplied
if (SpellAbilityCastMode.BESTOW.equals(this.getSpellAbility().getSpellAbilityCastMode())) {
SubTypes subtypes = card.getSubtype(game);
if (!subtypes.contains(SubType.AURA)) { // do it only once
subtypes.add(SubType.AURA);
}
return subtypes;
Card modifiedCard = this.getSpellAbility().getSpellAbilityCastMode().getTypeModifiedCardObjectCopy(card, this.getSpellAbility(), game);
return modifiedCard.getSubtype();
}
return card.getSubtype(game);
}
@Override
public boolean hasSubtype(SubType subtype, Game game) {
if (SpellAbilityCastMode.BESTOW.equals(this.getSpellAbility().getSpellAbilityCastMode())) { // workaround for Bestow (don't like it)
SubTypes subtypes = card.getSubtype(game);
if (!subtypes.contains(SubType.AURA)) { // do it only once
subtypes.add(SubType.AURA);
}
if (subtypes.contains(subtype)) {
return true;
}
if (SpellAbilityCastMode.BESTOW.equals(this.getSpellAbility().getSpellAbilityCastMode())) {
Card modifiedCard = this.getSpellAbility().getSpellAbilityCastMode().getTypeModifiedCardObjectCopy(card, this.getSpellAbility(), game);
return modifiedCard.hasSubtype(subtype, game);
}
return card.hasSubtype(subtype, game);
}