mirror of
https://github.com/magefree/mage.git
synced 2025-12-22 03:22:00 -08:00
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:
parent
ac20483b73
commit
5e095afdb0
23 changed files with 946 additions and 17 deletions
|
|
@ -31,8 +31,13 @@ public final class HulkingMetamorph extends CardImpl {
|
|||
blueprint.addCardType(CardType.CREATURE);
|
||||
Permanent permanent = game.getPermanentEntering(copyToObjectId);
|
||||
if (permanent != null) {
|
||||
blueprint.getPower().setModifiedBaseValue(permanent.getPower().getValue());
|
||||
blueprint.getToughness().setModifiedBaseValue(permanent.getToughness().getValue());
|
||||
int pt = permanent.isPrototyped()? 3 : 7;
|
||||
blueprint.getPower().setModifiedBaseValue(pt);
|
||||
blueprint.getToughness().setModifiedBaseValue(pt);
|
||||
//Would prefer the following code, but it doesn't seem to work correctly with Prototype as-is
|
||||
//Either need to change Prototype or fix the Blood Moon problem
|
||||
//blueprint.getPower().setModifiedBaseValue(permanent.getPower().getValue());
|
||||
//blueprint.getToughness().setModifiedBaseValue(permanent.getToughness().getValue());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,16 +6,12 @@ import mage.constants.Rarity;
|
|||
import mage.constants.SetType;
|
||||
import mage.util.RandomUtil;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author TheElk801
|
||||
*/
|
||||
public final class TheBrothersWar extends ExpansionSet {
|
||||
|
||||
private static final List<String> unfinished = Arrays.asList("Arcane Proxy", "Autonomous Assembler", "Blitz Automaton", "Boulderbranch Golem", "Combat Thresher", "Cradle Clearcutter", "Depth Charge Colossus", "Fallaji Dragon Engine", "Goring Warplow", "Hulking Metamorph", "Iron-Craw Crusher", "Phyrexian Fleshgorger", "Rootwire Amalgam", "Rust Goliath", "Skitterbeam Battalion", "Spotter Thopter", "Steel Seraph", "Woodcaller Automaton");
|
||||
|
||||
private static final TheBrothersWar instance = new TheBrothersWar();
|
||||
|
||||
public static TheBrothersWar getInstance() {
|
||||
|
|
@ -421,8 +417,6 @@ public final class TheBrothersWar extends ExpansionSet {
|
|||
cards.add(new SetCardInfo("Yotian Medic", 33, Rarity.COMMON, mage.cards.y.YotianMedic.class));
|
||||
cards.add(new SetCardInfo("Yotian Tactician", 228, Rarity.UNCOMMON, mage.cards.y.YotianTactician.class));
|
||||
cards.add(new SetCardInfo("Zephyr Sentinel", 74, Rarity.UNCOMMON, mage.cards.z.ZephyrSentinel.class));
|
||||
|
||||
cards.removeIf(setCardInfo -> unfinished.contains(setCardInfo.getName())); // remove when mechanic is implemented
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -0,0 +1,729 @@
|
|||
package org.mage.test.cards.abilities.keywords;
|
||||
|
||||
import mage.MageObject;
|
||||
import mage.ObjectColor;
|
||||
import mage.abilities.common.SpellCastControllerTriggeredAbility;
|
||||
import mage.abilities.effects.common.GainLifeEffect;
|
||||
import mage.abilities.keyword.HasteAbility;
|
||||
import mage.cards.Card;
|
||||
import mage.constants.ComparisonType;
|
||||
import mage.constants.PhaseStep;
|
||||
import mage.constants.Zone;
|
||||
import mage.filter.FilterSpell;
|
||||
import mage.filter.StaticFilters;
|
||||
import mage.filter.predicate.Predicate;
|
||||
import mage.filter.predicate.mageobject.*;
|
||||
import mage.game.permanent.Permanent;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import org.mage.test.serverside.base.CardTestPlayerBase;
|
||||
|
||||
/**
|
||||
* @author TheElk801
|
||||
*/
|
||||
public class PrototypeTest extends CardTestPlayerBase {
|
||||
|
||||
private static final String automaton = "Blitz Automaton";
|
||||
private static final String withPrototype = " using Prototype";
|
||||
private static final String automatonWithPrototype = automaton+withPrototype;
|
||||
private static final String bolt = "Lightning Bolt";
|
||||
private static final String cloudshift = "Cloudshift";
|
||||
private static final String clone = "Clone";
|
||||
private static final String counterpart = "Cackling Counterpart";
|
||||
private static final String epiphany = "Sublime Epiphany";
|
||||
private static final String denied = "Access Denied";
|
||||
|
||||
private void checkAutomaton(boolean prototyped) {
|
||||
checkAutomaton(prototyped, 1);
|
||||
}
|
||||
|
||||
private void checkAutomaton(boolean prototyped, int count) {
|
||||
assertPermanentCount(playerA, automaton, count);
|
||||
for (Permanent permanent : currentGame.getBattlefield().getActivePermanents(
|
||||
StaticFilters.FILTER_PERMANENT, playerA.getId(), currentGame
|
||||
)) {
|
||||
if (!permanent.getName().equals(automaton)) {
|
||||
continue;
|
||||
}
|
||||
Assert.assertTrue("Needs haste", permanent.getAbilities(currentGame).contains(HasteAbility.getInstance()));
|
||||
Assert.assertEquals("Power is wrong", prototyped ? 3 : 6, permanent.getPower().getValue());
|
||||
Assert.assertEquals("Toughness is wrong", prototyped ? 2 : 4, permanent.getToughness().getValue());
|
||||
Assert.assertTrue("Color is wrong", prototyped
|
||||
? permanent.getColor(currentGame).isRed()
|
||||
: permanent.getColor(currentGame).isColorless()
|
||||
);
|
||||
Assert.assertEquals("Mana cost is wrong", prototyped ? "{2}{R}" : "{7}", permanent.getManaCost().getText());
|
||||
Assert.assertEquals("Mana value is wrong", prototyped ? 3 : 7, permanent.getManaValue());
|
||||
}
|
||||
}
|
||||
|
||||
private void makeTester(Predicate<? super MageObject>... predicates) {
|
||||
FilterSpell filter = new FilterSpell();
|
||||
for (Predicate<? super MageObject> predicate : predicates) {
|
||||
filter.add(predicate);
|
||||
}
|
||||
addCustomCardWithAbility(
|
||||
"tester", playerA,
|
||||
new SpellCastControllerTriggeredAbility(
|
||||
new GainLifeEffect(1), filter, false
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNormal() {
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Wastes", 7);
|
||||
addCard(Zone.HAND, playerA, automaton);
|
||||
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, automaton);
|
||||
|
||||
setStopAt(1, PhaseStep.POSTCOMBAT_MAIN);
|
||||
setStrictChooseMode(true);
|
||||
execute();
|
||||
|
||||
checkAutomaton(false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPrototype() {
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 3);
|
||||
addCard(Zone.HAND, playerA, automaton);
|
||||
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, automatonWithPrototype);
|
||||
|
||||
setStopAt(1, PhaseStep.POSTCOMBAT_MAIN);
|
||||
setStrictChooseMode(true);
|
||||
execute();
|
||||
|
||||
checkAutomaton(true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLeavesBattlefield() {
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 3 + 1);
|
||||
addCard(Zone.HAND, playerA, automaton);
|
||||
addCard(Zone.HAND, playerA, bolt);
|
||||
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, automatonWithPrototype);
|
||||
|
||||
castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, bolt, automaton);
|
||||
|
||||
setStopAt(1, PhaseStep.END_TURN);
|
||||
setStrictChooseMode(true);
|
||||
execute();
|
||||
|
||||
assertGraveyardCount(playerA, automaton, 1);
|
||||
Card card = playerA
|
||||
.getGraveyard()
|
||||
.getCards(currentGame)
|
||||
.stream()
|
||||
.filter(c -> c.getName().equals(automaton))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
Assert.assertTrue("Card should be colorless", card.getColor(currentGame).isColorless());
|
||||
Assert.assertEquals("Card should have 6 power", 6, card.getPower().getValue());
|
||||
Assert.assertEquals("Card should have 4 toughness", 4, card.getToughness().getValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBlink() {
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Plateau", 3 + 1);
|
||||
addCard(Zone.HAND, playerA, automaton);
|
||||
addCard(Zone.HAND, playerA, cloudshift);
|
||||
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, automatonWithPrototype);
|
||||
|
||||
castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, cloudshift, automaton);
|
||||
|
||||
setStopAt(1, PhaseStep.END_TURN);
|
||||
setStrictChooseMode(true);
|
||||
execute();
|
||||
|
||||
checkAutomaton(false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTriggerColorlessSpell() {
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Wastes", 7);
|
||||
addCard(Zone.HAND, playerA, automaton);
|
||||
|
||||
makeTester(ColorlessPredicate.instance);
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, automaton);
|
||||
|
||||
setStopAt(1, PhaseStep.POSTCOMBAT_MAIN);
|
||||
setStrictChooseMode(true);
|
||||
execute();
|
||||
|
||||
assertLife(playerA, 20 + 1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTriggerRedSpell() {
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 3);
|
||||
addCard(Zone.HAND, playerA, automaton);
|
||||
|
||||
makeTester(new ColorPredicate(ObjectColor.RED));
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, automatonWithPrototype);
|
||||
|
||||
setStopAt(1, PhaseStep.POSTCOMBAT_MAIN);
|
||||
setStrictChooseMode(true);
|
||||
execute();
|
||||
|
||||
assertLife(playerA, 20 + 1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTrigger64Spell() {
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Wastes", 7);
|
||||
addCard(Zone.HAND, playerA, automaton);
|
||||
|
||||
makeTester(
|
||||
new PowerPredicate(ComparisonType.EQUAL_TO, 6),
|
||||
new ToughnessPredicate(ComparisonType.EQUAL_TO, 4)
|
||||
);
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, automaton);
|
||||
|
||||
setStopAt(1, PhaseStep.POSTCOMBAT_MAIN);
|
||||
setStrictChooseMode(true);
|
||||
execute();
|
||||
|
||||
assertLife(playerA, 20 + 1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTrigger32Spell() {
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 3);
|
||||
addCard(Zone.HAND, playerA, automaton);
|
||||
|
||||
makeTester(
|
||||
new PowerPredicate(ComparisonType.EQUAL_TO, 3),
|
||||
new ToughnessPredicate(ComparisonType.EQUAL_TO, 2)
|
||||
);
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, automatonWithPrototype);
|
||||
|
||||
setStopAt(1, PhaseStep.POSTCOMBAT_MAIN);
|
||||
setStrictChooseMode(true);
|
||||
execute();
|
||||
|
||||
assertLife(playerA, 20 + 1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTrigger7MVSpell() {
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Wastes", 7);
|
||||
addCard(Zone.HAND, playerA, automaton);
|
||||
|
||||
makeTester(new ManaValuePredicate(ComparisonType.EQUAL_TO, 7));
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, automaton);
|
||||
|
||||
setStopAt(1, PhaseStep.POSTCOMBAT_MAIN);
|
||||
setStrictChooseMode(true);
|
||||
execute();
|
||||
|
||||
assertLife(playerA, 20 + 1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTrigger3MVSpell() {
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 3);
|
||||
addCard(Zone.HAND, playerA, automaton);
|
||||
|
||||
makeTester(new ManaValuePredicate(ComparisonType.EQUAL_TO, 3));
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, automatonWithPrototype);
|
||||
|
||||
setStopAt(1, PhaseStep.POSTCOMBAT_MAIN);
|
||||
setStrictChooseMode(true);
|
||||
execute();
|
||||
|
||||
assertLife(playerA, 20 + 1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCloneRegular() {
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Volcanic Island", 7 + 4);
|
||||
addCard(Zone.HAND, playerA, automaton);
|
||||
addCard(Zone.HAND, playerA, clone);
|
||||
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, automaton);
|
||||
castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, clone);
|
||||
setChoice(playerA, true); // yes to clone
|
||||
setChoice(playerA, automaton);
|
||||
|
||||
setStopAt(1, PhaseStep.END_TURN);
|
||||
setStrictChooseMode(true);
|
||||
execute();
|
||||
|
||||
checkAutomaton(false, 2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testClonePrototype() {
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Volcanic Island", 3 + 4);
|
||||
addCard(Zone.HAND, playerA, automaton);
|
||||
addCard(Zone.HAND, playerA, clone);
|
||||
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, automatonWithPrototype);
|
||||
castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, clone);
|
||||
setChoice(playerA, true); // yes to clone
|
||||
setChoice(playerA, automaton);
|
||||
|
||||
setStopAt(1, PhaseStep.END_TURN);
|
||||
setStrictChooseMode(true);
|
||||
execute();
|
||||
|
||||
checkAutomaton(true, 2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTokenCopyRegular() {
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Volcanic Island", 7 + 3);
|
||||
addCard(Zone.HAND, playerA, automaton);
|
||||
addCard(Zone.HAND, playerA, counterpart);
|
||||
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, automaton);
|
||||
castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, counterpart, automaton);
|
||||
|
||||
setStopAt(1, PhaseStep.END_TURN);
|
||||
setStrictChooseMode(true);
|
||||
execute();
|
||||
|
||||
checkAutomaton(false, 2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTokenCopyPrototype() {
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Volcanic Island", 3 + 3);
|
||||
addCard(Zone.HAND, playerA, automaton);
|
||||
addCard(Zone.HAND, playerA, counterpart);
|
||||
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, automatonWithPrototype);
|
||||
castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, counterpart, automaton);
|
||||
|
||||
setStopAt(1, PhaseStep.END_TURN);
|
||||
setStrictChooseMode(true);
|
||||
execute();
|
||||
|
||||
checkAutomaton(true, 2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTokenCopyRegularLKI() {
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Island", 7 + 6);
|
||||
addCard(Zone.HAND, playerA, automaton);
|
||||
addCard(Zone.HAND, playerA, epiphany);
|
||||
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, automaton);
|
||||
castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, epiphany);
|
||||
setModeChoice(playerA, "3"); // Return target nonland permanent to its owner's hand.
|
||||
setModeChoice(playerA, "4"); // Create a token that's a copy of target creature you control.
|
||||
addTarget(playerA, automaton);
|
||||
addTarget(playerA, automaton);
|
||||
|
||||
setStopAt(1, PhaseStep.END_TURN);
|
||||
setStrictChooseMode(true);
|
||||
execute();
|
||||
|
||||
assertHandCount(playerA, automaton, 1);
|
||||
checkAutomaton(false, 1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTokenCopyPrototypeLKI() {
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Volcanic Island", 3 + 6);
|
||||
addCard(Zone.HAND, playerA, automaton);
|
||||
addCard(Zone.HAND, playerA, epiphany);
|
||||
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, automatonWithPrototype);
|
||||
castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, epiphany);
|
||||
setModeChoice(playerA, "3"); // Return target nonland permanent to its owner's hand.
|
||||
setModeChoice(playerA, "4"); // Create a token that's a copy of target creature you control.
|
||||
addTarget(playerA, automaton);
|
||||
addTarget(playerA, automaton);
|
||||
|
||||
setStopAt(1, PhaseStep.END_TURN);
|
||||
setStrictChooseMode(true);
|
||||
execute();
|
||||
|
||||
assertHandCount(playerA, automaton, 1);
|
||||
checkAutomaton(true, 1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStackToughnessPrototyped() {
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 3);
|
||||
addCard(Zone.HAND, playerA, automaton);
|
||||
addCard(Zone.BATTLEFIELD, playerB, "Island", 2);
|
||||
addCard(Zone.HAND, playerB, "Stern Scolding");
|
||||
// Counter target creature spell with power or toughness 2 or less.
|
||||
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, automatonWithPrototype);
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, "Stern Scolding");
|
||||
addTarget(playerB, automaton);
|
||||
|
||||
setStopAt(1, PhaseStep.END_TURN);
|
||||
setStrictChooseMode(true);
|
||||
execute();
|
||||
|
||||
assertGraveyardCount(playerA, automaton, 1);
|
||||
checkAutomaton(true, 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStackColorPrototyped() {
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 3);
|
||||
addCard(Zone.HAND, playerA, automaton);
|
||||
addCard(Zone.BATTLEFIELD, playerB, "Island", 2);
|
||||
addCard(Zone.BATTLEFIELD, playerB, "Douse");
|
||||
// {1}{U}: Counter target red spell.
|
||||
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, automatonWithPrototype);
|
||||
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerB, "{1}{U}: Counter target red spell");
|
||||
addTarget(playerB, automaton);
|
||||
|
||||
setStopAt(1, PhaseStep.END_TURN);
|
||||
setStrictChooseMode(true);
|
||||
execute();
|
||||
|
||||
assertGraveyardCount(playerA, automaton, 1);
|
||||
checkAutomaton(true, 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStackManaValueRegular() {
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Wastes", 7);
|
||||
addCard(Zone.HAND, playerA, automaton);
|
||||
addCard(Zone.BATTLEFIELD, playerB, "Island", 5);
|
||||
addCard(Zone.HAND, playerB, denied);
|
||||
// Counter target spell. Create X 1/1 colorless Thopter artifact creature tokens with flying, where X is that spell’s mana value.
|
||||
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, automaton);
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, denied, automaton);
|
||||
|
||||
setStopAt(1, PhaseStep.END_TURN);
|
||||
setStrictChooseMode(true);
|
||||
execute();
|
||||
|
||||
assertPermanentCount(playerB, "Thopter Token", 7);
|
||||
assertGraveyardCount(playerA, automaton, 1);
|
||||
checkAutomaton(false, 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStackManaValuePrototype() {
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 3);
|
||||
addCard(Zone.HAND, playerA, automaton);
|
||||
addCard(Zone.BATTLEFIELD, playerB, "Island", 5);
|
||||
addCard(Zone.HAND, playerB, denied);
|
||||
// Counter target spell. Create X 1/1 colorless Thopter artifact creature tokens with flying, where X is that spell’s mana value.
|
||||
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, automatonWithPrototype);
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, denied, automaton);
|
||||
|
||||
setStopAt(1, PhaseStep.END_TURN);
|
||||
setStrictChooseMode(true);
|
||||
execute();
|
||||
|
||||
assertPermanentCount(playerB, "Thopter Token", 3);
|
||||
assertGraveyardCount(playerA, automaton, 1);
|
||||
checkAutomaton(true, 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testManaValueWhenCasting() {
|
||||
String winnower = "Void Winnower"; // Your opponents can't cast spells with even mana values.
|
||||
String evenRegOddProto = "Fallaji Dragon Engine"; // {8} 5/5; {2}{R} 1/3
|
||||
String oddRegEvenProto = "Boulderbranch Golem"; // {7} 6/5; {3}{G} 3/3, ETB gain life equal to its power
|
||||
|
||||
addCard(Zone.BATTLEFIELD, playerB, winnower);
|
||||
addCard(Zone.HAND, playerA, evenRegOddProto);
|
||||
addCard(Zone.HAND, playerA, oddRegEvenProto);
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Wastes", 8);
|
||||
addCard(Zone.HAND, playerA, "Taiga");
|
||||
|
||||
// checkPlayableAbility doesn't seem to detect Void Winnower's restriction in time (probably because it checks CAST_SPELL_LATE?)
|
||||
// but if you try to actually cast a spell with even mana value, it will correctly fail
|
||||
|
||||
checkPlayableAbility("cast odd reg", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Boulderbranch", true);
|
||||
//checkPlayableAbility("cast even reg", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Fallaji", false);
|
||||
|
||||
playLand(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Taiga");
|
||||
|
||||
//checkPlayableAbility("cast even proto", 1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Cast Boulderbranch Golem"+withPrototype, false);
|
||||
checkPlayableAbility("cast odd proto", 1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Cast Fallaji Dragon Engine"+withPrototype, true);
|
||||
|
||||
castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, evenRegOddProto+withPrototype);
|
||||
|
||||
setStopAt(1, PhaseStep.END_TURN);
|
||||
setStrictChooseMode(true);
|
||||
execute();
|
||||
|
||||
assertPowerToughness(playerA, evenRegOddProto, 1, 3);
|
||||
}
|
||||
@Test
|
||||
public void testCopyOnStack() {
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Frontier Bivouac", 3+2);
|
||||
addCard(Zone.HAND, playerA, automaton);
|
||||
addCard(Zone.HAND, playerA, "Double Major");
|
||||
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, automatonWithPrototype);
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Double Major", automaton);
|
||||
|
||||
setStopAt(1, PhaseStep.END_TURN);
|
||||
setStrictChooseMode(true);
|
||||
execute();
|
||||
|
||||
checkAutomaton(true, 2);
|
||||
}
|
||||
@Test
|
||||
public void testHumility() {
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Plateau", 4+3+2);
|
||||
addCard(Zone.HAND, playerA, automaton);
|
||||
addCard(Zone.HAND, playerA, "Humility");
|
||||
addCard(Zone.HAND, playerA, "Disenchant");
|
||||
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Humility");
|
||||
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN);
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, automatonWithPrototype);
|
||||
|
||||
checkPT("Humility with Prototype", 1, PhaseStep.BEGIN_COMBAT, playerA, automaton, 1, 1);
|
||||
|
||||
castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Disenchant", "Humility");
|
||||
|
||||
setStopAt(1, PhaseStep.END_TURN);
|
||||
setStrictChooseMode(true);
|
||||
execute();
|
||||
|
||||
checkAutomaton(true);
|
||||
}
|
||||
@Test
|
||||
public void testColorCostReduction() {
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 2);
|
||||
addCard(Zone.HAND, playerA, automaton);
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Ruby Medallion");
|
||||
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, automatonWithPrototype);
|
||||
|
||||
setStopAt(1, PhaseStep.BEGIN_COMBAT);
|
||||
setStrictChooseMode(true);
|
||||
execute();
|
||||
|
||||
checkAutomaton(true);
|
||||
assertTappedCount("Mountain", true, 2);
|
||||
}
|
||||
@Test
|
||||
public void testAbilityRemovalPre() {
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Volcanic Island", 5);
|
||||
addCard(Zone.HAND, playerA, automaton);
|
||||
addCard(Zone.HAND, playerA, "Dress Down");
|
||||
|
||||
castSpell(1, PhaseStep.UPKEEP, playerA, "Dress Down");
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, automatonWithPrototype);
|
||||
|
||||
setStopAt(1, PhaseStep.BEGIN_COMBAT);
|
||||
setStrictChooseMode(true);
|
||||
execute();
|
||||
|
||||
assertPowerToughness(playerA, automaton, 3, 2);
|
||||
}
|
||||
@Test
|
||||
public void testAbilityRemovalPost() {
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Volcanic Island", 5);
|
||||
addCard(Zone.HAND, playerA, automaton);
|
||||
addCard(Zone.HAND, playerA, "Dress Down");
|
||||
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, automatonWithPrototype);
|
||||
castSpell(1, PhaseStep.BEGIN_COMBAT, playerA, "Dress Down");
|
||||
|
||||
setStopAt(1, PhaseStep.POSTCOMBAT_MAIN);
|
||||
setStrictChooseMode(true);
|
||||
execute();
|
||||
|
||||
assertPowerToughness(playerA, automaton, 3, 2);
|
||||
}
|
||||
@Test
|
||||
public void testEssenceOfWild() {
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Volcanic Island", 5);
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Essence of the Wild", 1);
|
||||
addCard(Zone.HAND, playerA, automaton);
|
||||
addCard(Zone.HAND, playerA, "Pyroclasm");
|
||||
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, automatonWithPrototype);
|
||||
castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Pyroclasm");
|
||||
|
||||
setStopAt(1, PhaseStep.END_TURN);
|
||||
setStrictChooseMode(true);
|
||||
execute();
|
||||
|
||||
assertPermanentCount(playerA, "Essence of the Wild", 2);
|
||||
}
|
||||
@Test
|
||||
public void testChainer() {
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Volcanic Island", 3);
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Chainer, Nightmare Adept", 1);
|
||||
addCard(Zone.GRAVEYARD, playerA, automaton);
|
||||
addCard(Zone.HAND, playerA, "Plains");
|
||||
|
||||
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Discard a card:");
|
||||
setChoice(playerA, "Plains");
|
||||
|
||||
castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, automatonWithPrototype);
|
||||
|
||||
setStopAt(1, PhaseStep.END_TURN);
|
||||
setStrictChooseMode(true);
|
||||
execute();
|
||||
|
||||
checkAutomaton(true);
|
||||
}
|
||||
@Test
|
||||
public void testMetamorphCopyA() {
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Volcanic Island", 3+9);
|
||||
addCard(Zone.HAND, playerA, automaton);
|
||||
addCard(Zone.HAND, playerA, "Hulking Metamorph");
|
||||
|
||||
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, automatonWithPrototype);
|
||||
castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Hulking Metamorph");
|
||||
setChoice(playerA, true);
|
||||
setChoice(playerA, automaton);
|
||||
|
||||
setStopAt(1, PhaseStep.END_TURN);
|
||||
setStrictChooseMode(true);
|
||||
execute();
|
||||
|
||||
assertPowerToughness(playerA, automaton, 3, 2);
|
||||
assertPowerToughness(playerA, automaton, 7, 7);
|
||||
}
|
||||
@Test
|
||||
public void testMetamorphCopyB() {
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Volcanic Island", 7+4);
|
||||
addCard(Zone.HAND, playerA, automaton);
|
||||
addCard(Zone.HAND, playerA, "Hulking Metamorph");
|
||||
|
||||
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, automaton);
|
||||
castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Hulking Metamorph"+withPrototype);
|
||||
setChoice(playerA, true);
|
||||
setChoice(playerA, automaton);
|
||||
|
||||
setStopAt(1, PhaseStep.END_TURN);
|
||||
setStrictChooseMode(true);
|
||||
execute();
|
||||
|
||||
assertPowerToughness(playerA, automaton, 6, 4);
|
||||
assertPowerToughness(playerA, automaton, 3, 3);
|
||||
}
|
||||
@Test
|
||||
public void testReflectionA() {
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Volcanic Island", 3+6+6);
|
||||
addCard(Zone.HAND, playerA, automaton);
|
||||
addCard(Zone.HAND, playerA, "Goring Warplow");
|
||||
addCard(Zone.HAND, playerA, "Infinite Reflection");
|
||||
|
||||
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, automatonWithPrototype);
|
||||
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN);
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Infinite Reflection", automaton);
|
||||
castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Goring Warplow");
|
||||
|
||||
setStopAt(1, PhaseStep.END_TURN);
|
||||
setStrictChooseMode(true);
|
||||
execute();
|
||||
|
||||
checkAutomaton(true, 2);
|
||||
}
|
||||
@Test
|
||||
public void testReflectionB() {
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Underground Sea", 7+6+2);
|
||||
addCard(Zone.HAND, playerA, automaton);
|
||||
addCard(Zone.HAND, playerA, "Goring Warplow");
|
||||
addCard(Zone.HAND, playerA, "Infinite Reflection");
|
||||
|
||||
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, automaton);
|
||||
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN);
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Infinite Reflection", automaton);
|
||||
castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Goring Warplow"+withPrototype);
|
||||
|
||||
setStopAt(1, PhaseStep.END_TURN);
|
||||
setStrictChooseMode(true);
|
||||
execute();
|
||||
|
||||
checkAutomaton(false, 2);
|
||||
}
|
||||
@Test
|
||||
public void testProgenitor() {
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Frontier Bivouac", 3+6);
|
||||
addCard(Zone.HAND, playerA, automaton);
|
||||
addCard(Zone.HAND, playerA, "Progenitor Mimic");
|
||||
|
||||
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, automatonWithPrototype);
|
||||
castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Progenitor Mimic");
|
||||
setChoice(playerA, true);
|
||||
setChoice(playerA, automaton);
|
||||
|
||||
setStopAt(3, PhaseStep.END_TURN);
|
||||
setStrictChooseMode(true);
|
||||
execute();
|
||||
|
||||
checkAutomaton(true, 3);
|
||||
}
|
||||
@Test
|
||||
public void testInstantaneousLKI() {
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Volcanic Island", 3+2);
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Flowstone Surge", 2);
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Drizzt Do'Urden", 1);
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Warstorm Surge", 1);
|
||||
addCard(Zone.HAND, playerA, automaton);
|
||||
addCard(Zone.HAND, playerA, "Slimebind");
|
||||
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Slimebind", "Drizzt Do'Urden");
|
||||
checkPT("Drizzt is shrunk",1, PhaseStep.BEGIN_COMBAT, playerA, "Drizzt Do'Urden",1, 1);
|
||||
castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, automatonWithPrototype); // 5/0
|
||||
setChoice(playerA, "Whenever a creature enters"); //Stack the trigger
|
||||
addTarget(playerA, playerB);
|
||||
|
||||
setStopAt(1, PhaseStep.END_TURN);
|
||||
setStrictChooseMode(true);
|
||||
execute();
|
||||
|
||||
assertPowerToughness(playerA, "Drizzt Do'Urden", 5, 5);
|
||||
assertGraveyardCount(playerA, automaton, 1);
|
||||
assertLife(playerB, 20-5);
|
||||
}
|
||||
@Test
|
||||
public void testReanimate() {
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Badlands", 3+1+1);
|
||||
addCard(Zone.HAND, playerA, automaton);
|
||||
addCard(Zone.HAND, playerA, "Cut Down");
|
||||
addCard(Zone.HAND, playerA, "Reanimate");
|
||||
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, automatonWithPrototype);
|
||||
castSpell(1, PhaseStep.BEGIN_COMBAT, playerA, "Cut Down", automaton);
|
||||
castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Reanimate", automaton);
|
||||
|
||||
setStopAt(1, PhaseStep.END_TURN);
|
||||
setStrictChooseMode(true);
|
||||
execute();
|
||||
|
||||
checkAutomaton(false);
|
||||
assertLife(playerA, 20-7);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* More tests suggested by Zerris:
|
||||
* 2) Gain control of spell on the stack: Aethersnatch
|
||||
* 7) Phasing: Slip Out the Back
|
||||
* 8) Alternate Cost: Fires of Invention (Cannot cast at all with fires on 3 lands, cannot cast prototyped even on 7)
|
||||
* NOTE: This test is probably wrong, Prototype is apparently NOT an alternate cost! https://magic.wizards.com/en/news/feature/comprehensive-rules-changes
|
||||
* 15) Yixlid Jailer + Chainer, Nightmare Adept - I believe you should be able to cast your card, but not Prototype it,
|
||||
* because that decision is made before it goes on the stack (and thus leaves the graveyard).
|
||||
* 19) Ensure Prototype is preserved through type changes - Swift Reconfiguration + Bludgeon Brawl on a prototyped card
|
||||
* (and attempt to equip to Master of Waves)
|
||||
* 20) Ensure colored mana in a Prototype cost is treated properly - can be paid for by Jegantha and Somberwald Sage,
|
||||
* reduced by Morophon but not Ugin, the Ineffable
|
||||
* 23) Jegantha can still be your companion with Depth Charge Colossus in your deck
|
||||
*/
|
||||
|
||||
}
|
||||
|
|
@ -116,6 +116,8 @@ public interface MageObject extends MageItem, Serializable, Copyable<MageObject>
|
|||
|
||||
ManaCosts<ManaCost> getManaCost();
|
||||
|
||||
void setManaCost(ManaCosts<ManaCost> costs);
|
||||
|
||||
default List<String> getManaCostSymbols() {
|
||||
List<String> symbols = new ArrayList<>();
|
||||
for (ManaCost cost : getManaCost()) {
|
||||
|
|
|
|||
|
|
@ -281,6 +281,11 @@ public abstract class MageObjectImpl implements MageObject {
|
|||
return manaCost;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setManaCost(ManaCosts<ManaCost> costs) {
|
||||
this.manaCost = costs.copy();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getManaValue() {
|
||||
if (manaCost != null) {
|
||||
|
|
|
|||
|
|
@ -442,6 +442,9 @@ public abstract class AbilityImpl implements Ability {
|
|||
// mandatory additional costs the spell has, such as that of Tormenting Voice. (2018-12-07)
|
||||
canUseAdditionalCost = true;
|
||||
break;
|
||||
case PROTOTYPE:
|
||||
// Notably, casting a spell as a prototype does not count as paying an alternative cost.
|
||||
// https://magic.wizards.com/en/news/feature/comprehensive-rules-changes
|
||||
case NORMAL:
|
||||
canUseAlternativeCost = true;
|
||||
canUseAdditionalCost = true;
|
||||
|
|
|
|||
|
|
@ -322,7 +322,7 @@ public class SpellAbility extends ActivatedAbilityImpl {
|
|||
}
|
||||
if (spellCharacteristics != null) {
|
||||
if (getSpellAbilityCastMode() != SpellAbilityCastMode.NORMAL) {
|
||||
spellCharacteristics = getSpellAbilityCastMode().getTypeModifiedCardObjectCopy(spellCharacteristics, game);
|
||||
spellCharacteristics = getSpellAbilityCastMode().getTypeModifiedCardObjectCopy(spellCharacteristics, this);
|
||||
}
|
||||
}
|
||||
return spellCharacteristics;
|
||||
|
|
|
|||
|
|
@ -146,6 +146,7 @@ public class CopyEffect extends ContinuousEffectImpl {
|
|||
//permanent.setSecondCardFace(targetPermanent.getSecondCardFace());
|
||||
permanent.setFlipCard(targetPermanent.isFlipCard());
|
||||
permanent.setFlipCardName(targetPermanent.getFlipCardName());
|
||||
permanent.setPrototyped(targetPermanent.isPrototyped());
|
||||
}
|
||||
|
||||
CardUtil.copySetAndCardNumber(permanent, copyFromObject);
|
||||
|
|
|
|||
|
|
@ -1,21 +1,48 @@
|
|||
package mage.abilities.keyword;
|
||||
|
||||
import mage.MageObject;
|
||||
import mage.ObjectColor;
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.SpellAbility;
|
||||
import mage.abilities.common.SimpleStaticAbility;
|
||||
import mage.abilities.costs.mana.ManaCostsImpl;
|
||||
import mage.abilities.effects.ContinuousEffectImpl;
|
||||
import mage.cards.Card;
|
||||
import mage.constants.*;
|
||||
import mage.game.Game;
|
||||
import mage.game.permanent.Permanent;
|
||||
|
||||
/**
|
||||
* @author TheElk801
|
||||
* @author TheElk801, Susucr, notgreat
|
||||
*/
|
||||
public class PrototypeAbility extends SpellAbility {
|
||||
|
||||
private final int power;
|
||||
private final int toughness;
|
||||
private final String manaString;
|
||||
private final String rule;
|
||||
|
||||
public PrototypeAbility(Card card, String manaString, int power, int toughness) {
|
||||
super(new ManaCostsImpl<>(manaString), card.getName());
|
||||
// TODO: implement this
|
||||
this.setSpellAbilityCastMode(SpellAbilityCastMode.PROTOTYPE);
|
||||
this.setTiming(TimingRule.SORCERY);
|
||||
this.addSubAbility(new SimpleStaticAbility(
|
||||
Zone.BATTLEFIELD, new PrototypeEffect(power, toughness, manaString)
|
||||
).setRuleVisible(false));
|
||||
this.rule = "Prototype " + manaString + " — " + power + "/" + toughness +
|
||||
" <i>(You may cast this spell with different mana cost, color, and size. It keeps its abilities and types.)</i>";
|
||||
setRuleAtTheTop(true);
|
||||
this.power = power;
|
||||
this.toughness = toughness;
|
||||
this.manaString = manaString;
|
||||
}
|
||||
|
||||
private PrototypeAbility(final PrototypeAbility ability) {
|
||||
super(ability);
|
||||
this.rule = ability.rule;
|
||||
this.power = ability.power;
|
||||
this.toughness = ability.toughness;
|
||||
this.manaString = ability.manaString;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -25,6 +52,68 @@ public class PrototypeAbility extends SpellAbility {
|
|||
|
||||
@Override
|
||||
public String getRule() {
|
||||
return "Prototype";
|
||||
return rule;
|
||||
}
|
||||
|
||||
//based on TransformAbility
|
||||
public Card prototypeCardSpell(Card original) {
|
||||
Card newCard = original.copy();
|
||||
newCard.setManaCost(new ManaCostsImpl<>(manaString));
|
||||
newCard.getPower().setModifiedBaseValue(power);
|
||||
newCard.getToughness().setModifiedBaseValue(toughness);
|
||||
newCard.getColor().setColor(new ObjectColor(manaString));
|
||||
return newCard;
|
||||
}
|
||||
|
||||
public void prototypePermanent(MageObject targetObject, Game game) {
|
||||
if (targetObject instanceof Permanent) {
|
||||
((Permanent)targetObject).setPrototyped(true);
|
||||
}
|
||||
targetObject.getColor(game).setColor(new ObjectColor(manaString));
|
||||
targetObject.setManaCost(new ManaCostsImpl<>(manaString));
|
||||
targetObject.getPower().setModifiedBaseValue(power);
|
||||
targetObject.getToughness().setModifiedBaseValue(toughness);
|
||||
}
|
||||
}
|
||||
|
||||
class PrototypeEffect extends ContinuousEffectImpl {
|
||||
|
||||
private final int power;
|
||||
private final int toughness;
|
||||
private final String manaString;
|
||||
private final ObjectColor color;
|
||||
|
||||
PrototypeEffect(int power, int toughness, String manaString) {
|
||||
super(Duration.EndOfGame, Layer.CopyEffects_1, SubLayer.CopyEffects_1a, Outcome.Benefit);
|
||||
this.power = power;
|
||||
this.toughness = toughness;
|
||||
this.manaString = manaString;
|
||||
this.color = new ObjectColor(manaString);
|
||||
}
|
||||
|
||||
private PrototypeEffect(final PrototypeEffect effect) {
|
||||
super(effect);
|
||||
this.power = effect.power;
|
||||
this.toughness = effect.toughness;
|
||||
this.manaString = effect.manaString;
|
||||
this.color = effect.color;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PrototypeEffect copy() {
|
||||
return new PrototypeEffect(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean apply(Game game, Ability source) {
|
||||
Permanent permanent = game.getPermanent(source.getSourceId());
|
||||
if (permanent == null || !permanent.isPrototyped()) {
|
||||
return false;
|
||||
}
|
||||
permanent.setManaCost(new ManaCostsImpl<>(manaString));
|
||||
permanent.getColor(game).setColor(color);
|
||||
permanent.getPower().setModifiedBaseValue(power);
|
||||
permanent.getToughness().setModifiedBaseValue(toughness);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,9 +1,10 @@
|
|||
package mage.constants;
|
||||
|
||||
import mage.abilities.SpellAbility;
|
||||
import mage.abilities.keyword.BestowAbility;
|
||||
import mage.abilities.keyword.MorphAbility;
|
||||
import mage.abilities.keyword.PrototypeAbility;
|
||||
import mage.cards.Card;
|
||||
import mage.game.Game;
|
||||
import mage.abilities.keyword.MorphAbility;
|
||||
import mage.game.stack.Spell;
|
||||
|
||||
/**
|
||||
|
|
@ -14,6 +15,7 @@ public enum SpellAbilityCastMode {
|
|||
MADNESS("Madness"),
|
||||
FLASHBACK("Flashback"),
|
||||
BESTOW("Bestow"),
|
||||
PROTOTYPE("Prototype"),
|
||||
MORPH("Morph"),
|
||||
TRANSFORMED("Transformed", true),
|
||||
DISTURB("Disturb", true),
|
||||
|
|
@ -42,7 +44,7 @@ public enum SpellAbilityCastMode {
|
|||
return text;
|
||||
}
|
||||
|
||||
public Card getTypeModifiedCardObjectCopy(Card card, Game game) {
|
||||
public Card getTypeModifiedCardObjectCopy(Card card, SpellAbility spellAbility) {
|
||||
Card cardCopy = card.copy();
|
||||
if (this.equals(BESTOW)) {
|
||||
BestowAbility.becomeAura(cardCopy);
|
||||
|
|
@ -53,6 +55,9 @@ public enum SpellAbilityCastMode {
|
|||
cardCopy = tmp.copy();
|
||||
}
|
||||
}
|
||||
if (this.equals(PROTOTYPE)) {
|
||||
cardCopy = ((PrototypeAbility) spellAbility).prototypeCardSpell(cardCopy);
|
||||
}
|
||||
if (this.equals(MORPH)) {
|
||||
if (cardCopy instanceof Spell) {
|
||||
//Spell doesn't support setName, so make a copy of the card (we're blowing it away anyway)
|
||||
|
|
|
|||
|
|
@ -133,6 +133,11 @@ public abstract class Designation extends MageObjectImpl {
|
|||
return emptyCost;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setManaCost(ManaCosts<ManaCost> costs) {
|
||||
throw new UnsupportedOperationException("Unsupported operation");
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getManaValue() {
|
||||
return 0;
|
||||
|
|
|
|||
|
|
@ -1978,6 +1978,14 @@ public abstract class GameImpl implements Game {
|
|||
if (copyFromPermanent.isTransformed()) {
|
||||
TransformAbility.transformPermanent(newBluePrint, newBluePrint.getSecondCardFace(), this, source);
|
||||
}
|
||||
if (copyFromPermanent.isPrototyped()) {
|
||||
Abilities<Ability> abilities = copyFromPermanent.getAbilities();
|
||||
for (Ability ability : abilities){
|
||||
if (ability instanceof PrototypeAbility) {
|
||||
((PrototypeAbility) ability).prototypePermanent(newBluePrint, this);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (applier != null) {
|
||||
applier.apply(this, newBluePrint, source, copyToPermanentId);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package mage.game;
|
||||
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.SpellAbility;
|
||||
import mage.abilities.keyword.TransformAbility;
|
||||
import mage.cards.*;
|
||||
import mage.constants.Outcome;
|
||||
|
|
@ -361,8 +362,16 @@ public final class ZonesHandler {
|
|||
// put onto battlefield with possible counters
|
||||
game.getPermanentsEntering().put(permanent.getId(), permanent);
|
||||
card.checkForCountersToAdd(permanent, source, game);
|
||||
|
||||
permanent.setTapped(info instanceof ZoneChangeInfo.Battlefield
|
||||
&& ((ZoneChangeInfo.Battlefield) info).tapped);
|
||||
|
||||
if (Zone.STACK == event.getFromZone()) {
|
||||
Spell spell = game.getStack().getSpell(event.getTargetId());
|
||||
if (spell != null) {
|
||||
permanent.setPrototyped(spell.isPrototyped());
|
||||
}
|
||||
}
|
||||
|
||||
permanent.setFaceDown(info.faceDown, game);
|
||||
if (info.faceDown) {
|
||||
|
|
|
|||
|
|
@ -238,6 +238,11 @@ public class Commander extends CommandObjectImpl {
|
|||
return sourceObject.getManaCost();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setManaCost(ManaCosts<ManaCost> costs) {
|
||||
throw new UnsupportedOperationException("Unsupported operation");
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getManaValue() {
|
||||
return sourceObject.getManaValue();
|
||||
|
|
|
|||
|
|
@ -255,6 +255,11 @@ public class Dungeon extends CommandObjectImpl {
|
|||
return emptyCost;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setManaCost(ManaCosts<ManaCost> costs) {
|
||||
throw new UnsupportedOperationException("Unsupported operation");
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getManaValue() {
|
||||
return 0;
|
||||
|
|
|
|||
|
|
@ -170,6 +170,11 @@ public abstract class Emblem extends CommandObjectImpl {
|
|||
return emptyCost;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setManaCost(ManaCosts<ManaCost> costs) {
|
||||
throw new UnsupportedOperationException("Unsupported operation");
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getManaValue() {
|
||||
return 0;
|
||||
|
|
|
|||
|
|
@ -195,6 +195,11 @@ public abstract class Plane extends CommandObjectImpl {
|
|||
return emptyCost;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setManaCost(ManaCosts<ManaCost> costs) {
|
||||
throw new UnsupportedOperationException("Unsupported operation");
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getManaValue() {
|
||||
return 0;
|
||||
|
|
|
|||
|
|
@ -75,6 +75,10 @@ public interface Permanent extends Card, Controllable {
|
|||
|
||||
void setRenowned(boolean value);
|
||||
|
||||
boolean isPrototyped();
|
||||
|
||||
void setPrototyped(boolean value);
|
||||
|
||||
int getClassLevel();
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -109,6 +109,7 @@ public abstract class PermanentImpl extends CardImpl implements Permanent {
|
|||
protected Map<String, String> info = new LinkedHashMap<>(); // additional info for permanent's rules
|
||||
protected int createOrder;
|
||||
protected boolean legendRuleApplies = true;
|
||||
protected boolean prototyped;
|
||||
|
||||
private static final List<UUID> emptyList = Collections.unmodifiableList(new ArrayList<>());
|
||||
|
||||
|
|
@ -179,6 +180,7 @@ public abstract class PermanentImpl extends CardImpl implements Permanent {
|
|||
this.morphed = permanent.morphed;
|
||||
this.manifested = permanent.manifested;
|
||||
this.createOrder = permanent.createOrder;
|
||||
this.prototyped = permanent.prototyped;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -1616,6 +1618,11 @@ public abstract class PermanentImpl extends CardImpl implements Permanent {
|
|||
return this.monstrous;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPrototyped() {
|
||||
return this.prototyped;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setMonstrous(boolean value) {
|
||||
this.monstrous = value;
|
||||
|
|
@ -1829,6 +1836,10 @@ public abstract class PermanentImpl extends CardImpl implements Permanent {
|
|||
this.secondSideCard = card;
|
||||
}
|
||||
|
||||
public void setPrototyped(boolean prototyped) {
|
||||
this.prototyped = prototyped;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRingBearer() {
|
||||
return ringBearerFlag;
|
||||
|
|
|
|||
|
|
@ -331,6 +331,11 @@ public abstract class TokenImpl extends MageObjectImpl implements Token {
|
|||
allAddedTokens.add((PermanentToken) permanent);
|
||||
}
|
||||
|
||||
// prototyped spell tokens make prototyped permanent tokens on resolution.
|
||||
if (source instanceof SpellAbility && ((SpellAbility) source).getSpellAbilityCastMode() == SpellAbilityCastMode.PROTOTYPE) {
|
||||
permanent.setPrototyped(true);
|
||||
}
|
||||
|
||||
// if token was created (not a spell copy) handle auras coming into the battlefield
|
||||
// that must determine what to enchant
|
||||
// see #9583 for the root cause issue of why this convoluted searching is necessary
|
||||
|
|
@ -345,6 +350,7 @@ public abstract class TokenImpl extends MageObjectImpl implements Token {
|
|||
if (!(ability instanceof SpellAbility)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auraOutcome = ability.getEffects().getOutcome(ability);
|
||||
for (Effect effect : ability.getEffects()) {
|
||||
if (!(effect instanceof AttachEffect)) {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -246,6 +246,11 @@ public class StackAbility extends StackObjectImpl implements Ability {
|
|||
return emptyCost;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setManaCost(ManaCosts<ManaCost> costs) {
|
||||
throw new UnsupportedOperationException("Unsupported operation");
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getManaCostSymbols() {
|
||||
return super.getManaCostSymbols();
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
package mage.util.functions;
|
||||
|
||||
import mage.MageObject;
|
||||
import mage.abilities.Abilities;
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.keyword.MorphAbility;
|
||||
import mage.abilities.keyword.PrototypeAbility;
|
||||
import mage.cards.Card;
|
||||
import mage.constants.CardType;
|
||||
import mage.constants.SuperType;
|
||||
|
|
@ -83,6 +85,14 @@ public class CopyTokenFunction {
|
|||
copyToToken(target.getBackFace(), ((Card) sourceObj).getSecondCardFace(), game);
|
||||
CardUtil.copySetAndCardNumber(target.getBackFace(), ((Card) sourceObj).getSecondCardFace());
|
||||
}
|
||||
if (((PermanentCard) source).isPrototyped()){
|
||||
Abilities<Ability> abilities = source.getAbilities();
|
||||
for (Ability ability : abilities){
|
||||
if (ability instanceof PrototypeAbility) {
|
||||
((PrototypeAbility) ability).prototypePermanent(target, game);
|
||||
}
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue