Implement Prototype ability (#11249)

Prototype is a SpellAbilityType, for which alternate costs are permitted. It has a continuous effect that applies on the battlefield as well as a method to modify the spell on the stack. Permanents have an isPrototyped flag that copy effects can check explicitly (same brittle method as transformed permanents use; reworking copy effects to streamline them is a separate scope).

Many test cases have been added to confirm functionality (thanks to Zerris for additional test suggestions).

---------

Co-authored-by: Susucre <34709007+Susucre@users.noreply.github.com>
Co-authored-by: Evan Kranzler <theelk801@gmail.com>
Co-authored-by: xenohedron <xenohedron@users.noreply.github.com>
This commit is contained in:
ssk97 2023-10-09 18:06:19 -07:00 committed by GitHub
parent ac20483b73
commit 5e095afdb0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
23 changed files with 946 additions and 17 deletions

View file

@ -10,6 +10,7 @@ import mage.abilities.costs.mana.ManaCost;
import mage.abilities.costs.mana.ManaCosts;
import mage.abilities.keyword.BestowAbility;
import mage.abilities.keyword.MorphAbility;
import mage.abilities.keyword.PrototypeAbility;
import mage.abilities.keyword.TransformAbility;
import mage.cards.*;
import mage.constants.*;
@ -46,6 +47,7 @@ public class Spell extends StackObjectImpl implements Card {
private final List<SpellAbility> spellAbilities = new ArrayList<>();
private final Card card;
private ManaCosts<ManaCost> manaCost;
private final ObjectColor color;
private final ObjectColor frameColor;
private final FrameStyle frameStyle;
@ -62,6 +64,7 @@ public class Spell extends StackObjectImpl implements Card {
private boolean resolving = false;
private UUID commandedByPlayerId = null; // controller of the spell resolve, example: Word of Command
private String commandedByInfo; // info about spell commanded, e.g. source
private boolean prototyped;
private int startingLoyalty;
private int startingDefense;
@ -79,8 +82,13 @@ public class Spell extends StackObjectImpl implements Card {
// simulate another side as new card (another code part in continues effect from disturb ability)
affectedCard = TransformAbility.transformCardSpellStatic(card, card.getSecondCardFace(), game);
}
if (ability instanceof PrototypeAbility){
affectedCard = ((PrototypeAbility)ability).prototypeCardSpell(card);
this.prototyped = true;
}
this.card = affectedCard;
this.manaCost = this.card.getManaCost().copy();
this.color = affectedCard.getColor(null).copy();
this.frameColor = affectedCard.getFrameColor(null).copy();
this.frameStyle = affectedCard.getFrameStyle();
@ -109,6 +117,7 @@ public class Spell extends StackObjectImpl implements Card {
} else {
spellAbilities.add(ability);
}
this.controllerId = controllerId;
this.fromZone = fromZone;
this.countered = false;
@ -128,6 +137,7 @@ public class Spell extends StackObjectImpl implements Card {
this.card = spell.card.copy();
this.fromZone = spell.fromZone;
this.manaCost = spell.getManaCost().copy();
this.color = spell.color.copy();
this.frameColor = spell.color.copy();
this.frameStyle = spell.frameStyle;
@ -143,6 +153,7 @@ public class Spell extends StackObjectImpl implements Card {
this.currentActivatingManaAbilitiesStep = spell.currentActivatingManaAbilitiesStep;
this.targetChanged = spell.targetChanged;
this.prototyped = spell.prototyped;
this.startingLoyalty = spell.startingLoyalty;
this.startingDefense = spell.startingDefense;
}
@ -632,9 +643,12 @@ public class Spell extends StackObjectImpl implements Card {
@Override
public ManaCosts<ManaCost> getManaCost() {
return card.getManaCost();
return this.manaCost;
}
@Override
public void setManaCost(ManaCosts<ManaCost> costs) { this.manaCost = costs.copy(); }
/**
* 202.3b When calculating the converted mana cost of an object with an {X}
* in its mana cost, X is treated as 0 while the object is not on the stack,
@ -652,7 +666,7 @@ public class Spell extends StackObjectImpl implements Card {
for (SpellAbility spellAbility : spellAbilities) {
cmc += spellAbility.getConvertedXManaCost(getCard());
}
cmc += getCard().getManaCost().manaValue();
cmc += this.manaCost.manaValue();
return cmc;
}
@ -789,6 +803,10 @@ public class Spell extends StackObjectImpl implements Card {
return false;
}
public boolean isPrototyped() {
return prototyped;
}
@Override
public Spell copy() {
return new Spell(this);