mirror of
https://github.com/magefree/mage.git
synced 2025-12-22 03:22:00 -08:00
Merge pull request #11417 from ssk97/TagTracking3_KeywordAbilities
Costs Tag Tracking part 3: Most keyword abilities
This commit is contained in:
commit
4977fea307
40 changed files with 510 additions and 404 deletions
|
|
@ -6,7 +6,7 @@ import mage.abilities.Ability;
|
||||||
import mage.abilities.common.CantBeCounteredSourceAbility;
|
import mage.abilities.common.CantBeCounteredSourceAbility;
|
||||||
import mage.abilities.common.EntersBattlefieldAbility;
|
import mage.abilities.common.EntersBattlefieldAbility;
|
||||||
import mage.abilities.effects.common.CopyPermanentEffect;
|
import mage.abilities.effects.common.CopyPermanentEffect;
|
||||||
import mage.abilities.effects.common.EntersBattlefieldWithXCountersEffect;
|
import mage.abilities.effects.common.counter.AddCountersSourceEffect;
|
||||||
import mage.cards.CardImpl;
|
import mage.cards.CardImpl;
|
||||||
import mage.cards.CardSetInfo;
|
import mage.cards.CardSetInfo;
|
||||||
import mage.constants.CardType;
|
import mage.constants.CardType;
|
||||||
|
|
@ -14,6 +14,7 @@ import mage.constants.SubType;
|
||||||
import mage.counters.CounterType;
|
import mage.counters.CounterType;
|
||||||
import mage.filter.StaticFilters;
|
import mage.filter.StaticFilters;
|
||||||
import mage.game.Game;
|
import mage.game.Game;
|
||||||
|
import mage.util.CardUtil;
|
||||||
import mage.util.functions.CopyApplier;
|
import mage.util.functions.CopyApplier;
|
||||||
|
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
@ -65,11 +66,21 @@ class AlteredEgoCopyApplier extends CopyApplier {
|
||||||
// effect is applied to that object after applying the copy effect with that exception, the
|
// effect is applied to that object after applying the copy effect with that exception, the
|
||||||
// exception’s effect doesn’t happen.
|
// exception’s effect doesn’t happen.
|
||||||
|
|
||||||
if (!isCopyOfCopy(source, blueprint, copyToObjectId)) {
|
if (!isCopyOfCopy(source, blueprint, copyToObjectId) && CardUtil.checkSourceCostsTagExists(game, source, "X")) {
|
||||||
// except it enters with an additional X +1/+1 counters on it
|
// except it enters with an additional X +1/+1 counters on it
|
||||||
blueprint.getAbilities().add(
|
blueprint.getAbilities().add(
|
||||||
new EntersBattlefieldAbility(new EntersBattlefieldWithXCountersEffect(CounterType.P1P1.createInstance()))
|
new EntersBattlefieldAbility(new AddCountersSourceEffect(CounterType.P1P1.createInstance(
|
||||||
|
CardUtil.getSourceCostsTag(game, source, "X", 0)
|
||||||
|
)))
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If the chosen creature has {X} in its mana cost, that X is considered to be 0.
|
||||||
|
* The value of X in Altered Ego's last ability will be whatever value was chosen for X while casting Altered Ego.
|
||||||
|
* (2016-04-08)
|
||||||
|
* So the X value of Altered Ego must be separate from the copied creature's X value
|
||||||
|
*/
|
||||||
|
CardUtil.getSourceCostsTagsMap(game, source).remove("X");
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|
|
||||||
|
|
@ -73,7 +73,7 @@ class StrongholdArenaGainLifeEffect extends OneShotEffect {
|
||||||
if (controller == null) {
|
if (controller == null) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
controller.gainLife(KickerAbility.getSourceObjectKickedCount(game, source) * 3, game, source);
|
controller.gainLife(KickerAbility.getKickedCounter(game, source) * 3, game, source);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -67,6 +67,48 @@ public class BlitzTest extends CardTestPlayerBase {
|
||||||
assertHandCount(playerA, 1);
|
assertHandCount(playerA, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testBlitzCopy() {
|
||||||
|
//Copying the spell on the stack must include the Blitz ability activation
|
||||||
|
addCard(Zone.BATTLEFIELD, playerA, "Tropical Island", 6);
|
||||||
|
addCard(Zone.HAND, playerA, decoy);
|
||||||
|
addCard(Zone.HAND, playerA, "Double Major");
|
||||||
|
|
||||||
|
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, decoy + withBlitz);
|
||||||
|
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Double Major",decoy);
|
||||||
|
|
||||||
|
setChoice(playerA, ""); //stack triggers
|
||||||
|
|
||||||
|
setStrictChooseMode(true);
|
||||||
|
setStopAt(1, PhaseStep.END_TURN);
|
||||||
|
execute();
|
||||||
|
|
||||||
|
assertPermanentCount(playerA, decoy, 0);
|
||||||
|
assertGraveyardCount(playerA, decoy, 1);
|
||||||
|
assertGraveyardCount(playerA, "Double Major", 1);
|
||||||
|
assertHandCount(playerA, 2);
|
||||||
|
}
|
||||||
|
@Test
|
||||||
|
public void testBlitzClone() {
|
||||||
|
//Copying the creature permanent must not include Blitz activation
|
||||||
|
addCard(Zone.BATTLEFIELD, playerA, "Tropical Island", 8);
|
||||||
|
addCard(Zone.HAND, playerA, decoy);
|
||||||
|
addCard(Zone.HAND, playerA, "Clone");
|
||||||
|
|
||||||
|
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, decoy + withBlitz);
|
||||||
|
waitStackResolved(1,PhaseStep.PRECOMBAT_MAIN);
|
||||||
|
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Clone");
|
||||||
|
setChoice(playerA,true);
|
||||||
|
setChoice(playerA,decoy);
|
||||||
|
|
||||||
|
setStrictChooseMode(true);
|
||||||
|
setStopAt(1, PhaseStep.END_TURN);
|
||||||
|
execute();
|
||||||
|
|
||||||
|
assertPermanentCount(playerA, decoy, 1);
|
||||||
|
assertGraveyardCount(playerA, decoy, 1);
|
||||||
|
assertHandCount(playerA, 1);
|
||||||
|
}
|
||||||
@Test
|
@Test
|
||||||
public void testNoBlitz() {
|
public void testNoBlitz() {
|
||||||
addCard(Zone.BATTLEFIELD, playerA, "Forest", 2);
|
addCard(Zone.BATTLEFIELD, playerA, "Forest", 2);
|
||||||
|
|
|
||||||
|
|
@ -70,5 +70,41 @@ public class EchoTest extends CardTestPlayerBase {
|
||||||
assertTappedCount("Mountain", true, 0);
|
assertTappedCount("Mountain", true, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Deranged Hermit has been cloned with Phantasmal Image.
|
||||||
|
//The Phantasmal Image version of the Deranged Hermit had to pay the echo cost multiple times.
|
||||||
|
@Test
|
||||||
|
public void testEchoTriggerClone() {
|
||||||
|
addCard(Zone.BATTLEFIELD, playerA, "Tropical Island", 15);
|
||||||
|
// Deranged Hermit {3}{G}{G}
|
||||||
|
// Echo
|
||||||
|
addCard(Zone.HAND, playerA, "Deranged Hermit");
|
||||||
|
addCard(Zone.HAND, playerA, "Phantasmal Image");
|
||||||
|
addCard(Zone.HAND, playerA, "Double Major");
|
||||||
|
|
||||||
|
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Deranged Hermit");
|
||||||
|
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Double Major", "Deranged Hermit");
|
||||||
|
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN);
|
||||||
|
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Phantasmal Image");
|
||||||
|
setChoice(playerA, true);
|
||||||
|
setChoice(playerA, "Deranged Hermit");
|
||||||
|
|
||||||
|
setChoice(playerA, ""); //stack triggers
|
||||||
|
setChoice(playerA, "");
|
||||||
|
|
||||||
|
setChoice(playerA, true); //Pay echo costs
|
||||||
|
setChoice(playerA, true);
|
||||||
|
setChoice(playerA, true);
|
||||||
|
|
||||||
|
setStrictChooseMode(true);
|
||||||
|
setStopAt(3, PhaseStep.PRECOMBAT_MAIN);
|
||||||
|
execute();
|
||||||
|
|
||||||
|
assertLife(playerA, 20);
|
||||||
|
assertLife(playerB, 20);
|
||||||
|
|
||||||
|
assertPermanentCount(playerA, "Deranged Hermit", 3);
|
||||||
|
assertTappedCount("Tropical Island", true, 15);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,150 @@
|
||||||
|
|
||||||
|
package org.mage.test.cards.abilities.keywords;
|
||||||
|
|
||||||
|
import mage.constants.PhaseStep;
|
||||||
|
import mage.constants.Zone;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.mage.test.serverside.base.CardTestPlayerBase;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author notgreat
|
||||||
|
*/
|
||||||
|
public class SpectacleTest extends CardTestPlayerBase {
|
||||||
|
@Test
|
||||||
|
public void testWithoutSpectacleBasic() {
|
||||||
|
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 1+4);
|
||||||
|
addCard(Zone.HAND, playerA, "Lightning Bolt"); // {R}
|
||||||
|
addCard(Zone.HAND, playerA, "Spikewheel Acrobat"); // {3}{R}, Spectacle {2}{R}
|
||||||
|
|
||||||
|
checkPlayableAbility("Can't cast with Spectacle yet", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Spikewheel Acrobat with spectacle", false);
|
||||||
|
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Lightning Bolt", playerB);
|
||||||
|
waitStackResolved(1,PhaseStep.PRECOMBAT_MAIN);
|
||||||
|
checkPlayableAbility("Can cast with Spectacle", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Spikewheel Acrobat with spectacle", true);
|
||||||
|
|
||||||
|
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Spikewheel Acrobat");
|
||||||
|
|
||||||
|
setStopAt(1, PhaseStep.BEGIN_COMBAT);
|
||||||
|
setStrictChooseMode(true);
|
||||||
|
execute();
|
||||||
|
|
||||||
|
assertPermanentCount(playerA,"Spikewheel Acrobat",1);
|
||||||
|
assertTappedCount("Mountain",true,1+4);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testWithoutSpectacleTriggerAfterDamage() {
|
||||||
|
// Rafter Demon {2}{B}{R}
|
||||||
|
// Spectacle {3}{B}{R}
|
||||||
|
// When Rafter Demon enters the battlefield, if its spectacle cost was paid, each opponent discards a card.
|
||||||
|
addCard(Zone.BATTLEFIELD, playerA, "Badlands", 6);
|
||||||
|
addCard(Zone.HAND, playerA, "Lightning Bolt"); // {R}
|
||||||
|
addCard(Zone.HAND, playerA, "Rafter Demon"); // {2}{B}{R}
|
||||||
|
|
||||||
|
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Lightning Bolt", playerB);
|
||||||
|
waitStackResolved(1,PhaseStep.PRECOMBAT_MAIN);
|
||||||
|
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Rafter Demon");
|
||||||
|
|
||||||
|
setStopAt(1, PhaseStep.BEGIN_COMBAT);
|
||||||
|
setStrictChooseMode(true);
|
||||||
|
execute();
|
||||||
|
|
||||||
|
assertPermanentCount(playerA,"Rafter Demon",1);
|
||||||
|
assertTappedCount("Badlands",true,5);
|
||||||
|
assertGraveyardCount(playerA, "Lightning Bolt", 1);
|
||||||
|
assertLife(playerB, 17);
|
||||||
|
assertGraveyardCount(playerB, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testWithSpectacle() {
|
||||||
|
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 4);
|
||||||
|
addCard(Zone.HAND, playerA, "Lightning Bolt"); // {R}
|
||||||
|
addCard(Zone.HAND, playerA, "Spikewheel Acrobat"); // Spectacle {2}{R}
|
||||||
|
|
||||||
|
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Lightning Bolt",playerB);
|
||||||
|
waitStackResolved(1,PhaseStep.PRECOMBAT_MAIN);
|
||||||
|
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Spikewheel Acrobat with spectacle");
|
||||||
|
|
||||||
|
setStopAt(1, PhaseStep.BEGIN_COMBAT);
|
||||||
|
setStrictChooseMode(true);
|
||||||
|
execute();
|
||||||
|
|
||||||
|
assertPermanentCount(playerA,"Spikewheel Acrobat",1);
|
||||||
|
assertTappedCount("Mountain",true,4);
|
||||||
|
assertLife(playerB, 17);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testRafterDemonCopyClone() {
|
||||||
|
addCard(Zone.BATTLEFIELD, playerA, "Badlands", 3);
|
||||||
|
addCard(Zone.BATTLEFIELD, playerA, "Tropical Island", 4);
|
||||||
|
addCard(Zone.BATTLEFIELD, playerA, "Wastes", 5);
|
||||||
|
// Rafter Demon {2}{B}{R}
|
||||||
|
// Spectacle {3}{B}{R}
|
||||||
|
// When Rafter Demon enters the battlefield, if its spectacle cost was paid, each opponent discards a card.
|
||||||
|
addCard(Zone.HAND, playerA, "Rafter Demon");
|
||||||
|
addCard(Zone.HAND, playerA, "Lightning Bolt");
|
||||||
|
addCard(Zone.HAND, playerB, "Darksteel Relic",5);
|
||||||
|
|
||||||
|
addCard(Zone.HAND, playerA, "Double Major");
|
||||||
|
addCard(Zone.HAND, playerA, "Clone");
|
||||||
|
|
||||||
|
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Lightning Bolt", playerB);
|
||||||
|
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN);
|
||||||
|
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Rafter Demon with spectacle");
|
||||||
|
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Double Major");
|
||||||
|
addTarget(playerA, "Rafter Demon");
|
||||||
|
addTarget(playerB, "Darksteel Relic",2);
|
||||||
|
|
||||||
|
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN);
|
||||||
|
checkGraveyardCount("Discard x2", 1, PhaseStep.PRECOMBAT_MAIN, playerB, "Darksteel Relic", 2);
|
||||||
|
checkPermanentCount("Rafter Demon x2", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Rafter Demon", 2);
|
||||||
|
|
||||||
|
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Clone");
|
||||||
|
setChoice(playerA, true); // copy
|
||||||
|
setChoice(playerA, "Rafter Demon");
|
||||||
|
setStopAt(1, PhaseStep.BEGIN_COMBAT);
|
||||||
|
setStrictChooseMode(true);
|
||||||
|
execute();
|
||||||
|
|
||||||
|
assertGraveyardCount(playerA, "Lightning Bolt", 1);
|
||||||
|
assertGraveyardCount(playerB, 2);
|
||||||
|
assertHandCount(playerB, "Darksteel Relic", 3);
|
||||||
|
assertPermanentCount(playerA, "Rafter Demon", 3);
|
||||||
|
|
||||||
|
assertLife(playerB, 17);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void SnapcasterMageWithSpectacle() {
|
||||||
|
//Should not be castable with Spectacle on flashback, since that's two alternative casts at once
|
||||||
|
addCard(Zone.BATTLEFIELD, playerA, "Volcanic Island", 3+2+2);
|
||||||
|
|
||||||
|
addCard(Zone.HAND, playerA, "Snapcaster Mage", 1);
|
||||||
|
addCard(Zone.HAND, playerA, "Skewer the Critics", 1);
|
||||||
|
addCard(Zone.HAND, playerA, "Snapcaster Mage", 1);
|
||||||
|
addCard(Zone.HAND, playerA, "Pyretic Ritual", 1);
|
||||||
|
|
||||||
|
setStrictChooseMode(true);
|
||||||
|
|
||||||
|
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Skewer the Critics", playerB);
|
||||||
|
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN, playerA);
|
||||||
|
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Snapcaster Mage");
|
||||||
|
addTarget(playerA, "Skewer the Critics");
|
||||||
|
|
||||||
|
checkPlayableAbility("No flashback with Spectacle available", 1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Flashback", false );
|
||||||
|
|
||||||
|
castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Pyretic Ritual", true);
|
||||||
|
activateAbility(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Flashback");
|
||||||
|
addTarget(playerA, playerB);
|
||||||
|
|
||||||
|
setStopAt(1, PhaseStep.END_TURN);
|
||||||
|
setStrictChooseMode(true);
|
||||||
|
execute();
|
||||||
|
|
||||||
|
assertLife(playerB, 20-3-3);
|
||||||
|
assertTappedCount("Volcanic Island", true, 3+2+2);
|
||||||
|
assertExileCount("Skewer the Critics", 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -280,9 +280,7 @@ public class SquadTest extends CardTestPlayerBase {
|
||||||
assertPermanentCount(playerA, flagellant, 3); // One original + its squad buddy + the squad buddy from the additional trigger
|
assertPermanentCount(playerA, flagellant, 3); // One original + its squad buddy + the squad buddy from the additional trigger
|
||||||
}
|
}
|
||||||
|
|
||||||
@Ignore
|
|
||||||
@Test
|
@Test
|
||||||
//TODO: Enable after fixing clones activating it if they have the same zcc. Also affects Kicker
|
|
||||||
public void test_CloneMustNotCopySquad() {
|
public void test_CloneMustNotCopySquad() {
|
||||||
addCard(Zone.BATTLEFIELD, playerA, swamp, 8); // 3 + 2 + 3
|
addCard(Zone.BATTLEFIELD, playerA, swamp, 8); // 3 + 2 + 3
|
||||||
addCard(Zone.BATTLEFIELD, playerA, "Island", 1);
|
addCard(Zone.BATTLEFIELD, playerA, "Island", 1);
|
||||||
|
|
|
||||||
|
|
@ -53,4 +53,34 @@ public class AlteredEgoTest extends CardTestPlayerBase {
|
||||||
assertGraveyardCount(playerA, "Altered Ego", 1);
|
assertGraveyardCount(playerA, "Altered Ego", 1);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If the chosen creature has {X} in its mana cost, that X is considered to be 0.
|
||||||
|
* The value of X in Altered Ego's last ability will be whatever value was chosen for X while casting Altered Ego.
|
||||||
|
* (2016-04-08)
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void copyXCreature() {
|
||||||
|
addCard(Zone.BATTLEFIELD, playerA, "Wastes", 2);
|
||||||
|
addCard(Zone.BATTLEFIELD, playerB, "Tropical Island", 7);
|
||||||
|
addCard(Zone.HAND, playerA, "Endless One"); // {X}, ETB with X +1/+1 counters, 0/0
|
||||||
|
addCard(Zone.HAND, playerB, "Altered Ego"); // {X}{2}{G}{U}
|
||||||
|
|
||||||
|
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Endless One");
|
||||||
|
setChoice(playerA, "X=2");
|
||||||
|
castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Altered Ego");
|
||||||
|
setChoice(playerB, "X=3");
|
||||||
|
setChoice(playerB, true); // use copy
|
||||||
|
setChoice(playerB, "Endless One"); // copy target
|
||||||
|
setChoice(playerB, ""); // Order place counters effects
|
||||||
|
|
||||||
|
setStrictChooseMode(true);
|
||||||
|
setStopAt(2, PhaseStep.BEGIN_COMBAT);
|
||||||
|
execute();
|
||||||
|
|
||||||
|
assertPermanentCount(playerA, "Endless One", 1);
|
||||||
|
assertPowerToughness(playerA, "Endless One", 2, 2);
|
||||||
|
assertPermanentCount(playerB, "Endless One", 1);
|
||||||
|
assertPowerToughness(playerB, "Endless One", 3, 3); //The X should not be copied
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -113,7 +113,6 @@ public class CopyPermanentSpellTest extends CardTestPlayerBase {
|
||||||
assertPowerToughness(playerA, "Aether Figment", 3, 3, Filter.ComparisonScope.All);
|
assertPowerToughness(playerA, "Aether Figment", 3, 3, Filter.ComparisonScope.All);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Ignore // currently fails
|
|
||||||
@Test
|
@Test
|
||||||
public void testSurgeTrigger() {
|
public void testSurgeTrigger() {
|
||||||
makeTester();
|
makeTester();
|
||||||
|
|
@ -121,7 +120,7 @@ public class CopyPermanentSpellTest extends CardTestPlayerBase {
|
||||||
addCard(Zone.HAND, playerA, "Memnite");
|
addCard(Zone.HAND, playerA, "Memnite");
|
||||||
addCard(Zone.HAND, playerA, "Reckless Bushwhacker");
|
addCard(Zone.HAND, playerA, "Reckless Bushwhacker");
|
||||||
|
|
||||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Memnite");
|
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Memnite", true);
|
||||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Reckless Bushwhacker with surge");
|
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Reckless Bushwhacker with surge");
|
||||||
|
|
||||||
setStopAt(1, PhaseStep.END_TURN);
|
setStopAt(1, PhaseStep.END_TURN);
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,108 @@
|
||||||
|
package org.mage.test.cards.copy;
|
||||||
|
|
||||||
|
import mage.constants.PhaseStep;
|
||||||
|
import mage.constants.Zone;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.mage.test.serverside.base.CardTestPlayerBase;
|
||||||
|
/**
|
||||||
|
* @author notgreat
|
||||||
|
*/
|
||||||
|
public class CostTagCopyCloneTests extends CardTestPlayerBase {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void KickerETBCountersClone() {
|
||||||
|
addCard(Zone.BATTLEFIELD, playerA, "Underground Sea", 11);
|
||||||
|
addCard(Zone.HAND, playerA, "Aether Figment");
|
||||||
|
addCard(Zone.HAND, playerA, "Clone");
|
||||||
|
addCard(Zone.HAND, playerA, "Shrivel");
|
||||||
|
|
||||||
|
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Aether Figment");
|
||||||
|
setChoice(playerA, true); // with Kicker
|
||||||
|
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN, false);
|
||||||
|
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Clone");
|
||||||
|
setChoice(playerA, true); // use copy
|
||||||
|
setChoice(playerA, "Aether Figment"); // copy target
|
||||||
|
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN, false);
|
||||||
|
|
||||||
|
// since Clone wasn't kicked, it's a 1/1. Cast Shrivel to easily separate
|
||||||
|
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Shrivel");
|
||||||
|
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN, false);
|
||||||
|
|
||||||
|
setStrictChooseMode(true);
|
||||||
|
setStopAt(1, PhaseStep.BEGIN_COMBAT);
|
||||||
|
execute();
|
||||||
|
|
||||||
|
assertPermanentCount(playerA, "Aether Figment", 1);
|
||||||
|
assertGraveyardCount(playerA, "Clone", 1);
|
||||||
|
}
|
||||||
|
@Test
|
||||||
|
public void XCountersCopyClone() {
|
||||||
|
addCard(Zone.BATTLEFIELD, playerA, "Tropical Island", 7);
|
||||||
|
addCard(Zone.HAND, playerA, "Endless One");
|
||||||
|
addCard(Zone.HAND, playerA, "Clone");
|
||||||
|
addCard(Zone.HAND, playerA, "Double Major");
|
||||||
|
|
||||||
|
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Endless One");
|
||||||
|
setChoice(playerA, "X=1");
|
||||||
|
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Double Major");
|
||||||
|
addTarget(playerA, "Endless One");
|
||||||
|
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN);
|
||||||
|
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Clone");
|
||||||
|
setChoice(playerA, true);
|
||||||
|
setChoice(playerA, "Endless One"); // since Clone doesn't copy X, it's a 0/0 and dies
|
||||||
|
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN);
|
||||||
|
|
||||||
|
|
||||||
|
setStrictChooseMode(true);
|
||||||
|
setStopAt(1, PhaseStep.BEGIN_COMBAT);
|
||||||
|
execute();
|
||||||
|
|
||||||
|
assertPermanentCount(playerA, "Endless One", 2);
|
||||||
|
assertGraveyardCount(playerA, "Clone", 1);
|
||||||
|
}
|
||||||
|
@Test
|
||||||
|
public void ETBXClone() {
|
||||||
|
addCard(Zone.BATTLEFIELD, playerA, "Tundra", 4+4);
|
||||||
|
addCard(Zone.HAND, playerA, "Defenders of Humanity");
|
||||||
|
addCard(Zone.HAND, playerA, "Clever Impersonator");
|
||||||
|
|
||||||
|
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Defenders of Humanity");
|
||||||
|
setChoice(playerA, "X=1");
|
||||||
|
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN, false);
|
||||||
|
|
||||||
|
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Clever Impersonator");
|
||||||
|
setChoice(playerA, true);
|
||||||
|
setChoice(playerA, "Defenders of Humanity"); //clones don't copy X
|
||||||
|
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN, false);
|
||||||
|
|
||||||
|
|
||||||
|
setStrictChooseMode(true);
|
||||||
|
setStopAt(1, PhaseStep.BEGIN_COMBAT);
|
||||||
|
execute();
|
||||||
|
|
||||||
|
assertPermanentCount(playerA,"Defenders of Humanity",2);
|
||||||
|
assertPermanentCount(playerA,"Astartes Warrior Token",1);
|
||||||
|
}
|
||||||
|
@Test
|
||||||
|
public void ETBXCopy() {
|
||||||
|
addCard(Zone.BATTLEFIELD, playerA, "Tundra", 4+1);
|
||||||
|
addCard(Zone.HAND, playerA, "Defenders of Humanity");
|
||||||
|
addCard(Zone.BATTLEFIELD, playerA, "Overloaded Mage-Ring");
|
||||||
|
|
||||||
|
assertPermanentCount(playerA,"Astartes Warrior Token",0);
|
||||||
|
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Defenders of Humanity");
|
||||||
|
setChoice(playerA, "X=1");
|
||||||
|
|
||||||
|
activateAbility(1,PhaseStep.PRECOMBAT_MAIN,playerA,
|
||||||
|
"{1}, {T}, Sacrifice {this}: Copy target spell you control.",
|
||||||
|
"Defenders of Humanity");
|
||||||
|
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN);
|
||||||
|
|
||||||
|
setStrictChooseMode(true);
|
||||||
|
setStopAt(1, PhaseStep.BEGIN_COMBAT);
|
||||||
|
execute();
|
||||||
|
|
||||||
|
assertPermanentCount(playerA,"Defenders of Humanity",2);
|
||||||
|
assertPermanentCount(playerA,"Astartes Warrior Token",2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -159,24 +159,21 @@ public interface Ability extends Controllable, Serializable {
|
||||||
void addManaCostsToPay(ManaCost manaCost);
|
void addManaCostsToPay(ManaCost manaCost);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets a map of the cost tags (set while casting/activating) of this ability, can be null if no tags have been set yet
|
* Gets a map of the cost tags (set while casting/activating) of this ability, can be null if no tags have been set yet.
|
||||||
* does NOT return the source permanent's tags
|
* Does NOT return the source permanent's tags.
|
||||||
|
* You should not be using this function in implementation of cards,
|
||||||
|
* this is a backing data structure used for internal storage.
|
||||||
|
* Use CardUtil {@link mage.util.CardUtil#getSourceCostsTag getSourceCostsTag} or {@link mage.util.CardUtil#checkSourceCostsTagExists checkSourceCostsTagExists} instead
|
||||||
*
|
*
|
||||||
* @return The map of tags and corresponding objects
|
* @return The map of tags and corresponding objects
|
||||||
*/
|
*/
|
||||||
Map<String, Object> getCostsTagMap();
|
Map<String, Object> getCostsTagMap();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set tag to the value, initializes this ability's tags map if it is null
|
* Set tag for this ability to the value, initializes this ability's tags map if needed.
|
||||||
|
* Should only be used from an {@link ActivatedAbility} (including {@link SpellAbility})
|
||||||
*/
|
*/
|
||||||
void setCostsTag(String tag, Object value);
|
void setCostsTag(String tag, Object value);
|
||||||
/**
|
|
||||||
* Returns the value of the tag or defaultValue if the tag is not found in this ability's tag map
|
|
||||||
* does NOT check the source permanent's tags, use CardUtil.getSourceCostsTag for that
|
|
||||||
*
|
|
||||||
* @return The given tag value (or the default if not found)
|
|
||||||
*/
|
|
||||||
Object getCostsTagOrDefault(String tag, Object defaultValue);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves the effects that are put into the place by the resolution of
|
* Retrieves the effects that are put into the place by the resolution of
|
||||||
|
|
|
||||||
|
|
@ -706,11 +706,6 @@ public abstract class AbilityImpl implements Ability {
|
||||||
return manaCostsToPay;
|
return manaCostsToPay;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Accessed to see what was optional/variable costs were paid
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public Map<String, Object> getCostsTagMap() {
|
public Map<String, Object> getCostsTagMap() {
|
||||||
return costsTagMap;
|
return costsTagMap;
|
||||||
|
|
@ -721,12 +716,6 @@ public abstract class AbilityImpl implements Ability {
|
||||||
}
|
}
|
||||||
costsTagMap.put(tag, value);
|
costsTagMap.put(tag, value);
|
||||||
}
|
}
|
||||||
public Object getCostsTagOrDefault(String tag, Object defaultValue){
|
|
||||||
if (costsTagMap != null && costsTagMap.containsKey(tag)){
|
|
||||||
return costsTagMap.get(tag);
|
|
||||||
}
|
|
||||||
return defaultValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Effects getEffects() {
|
public Effects getEffects() {
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,10 @@
|
||||||
package mage.abilities.condition.common;
|
package mage.abilities.condition.common;
|
||||||
|
|
||||||
import mage.MageObject;
|
|
||||||
import mage.abilities.Ability;
|
import mage.abilities.Ability;
|
||||||
import mage.abilities.condition.Condition;
|
import mage.abilities.condition.Condition;
|
||||||
import mage.abilities.keyword.BargainAbility;
|
import mage.abilities.keyword.BargainAbility;
|
||||||
import mage.cards.Card;
|
|
||||||
import mage.game.Game;
|
import mage.game.Game;
|
||||||
|
import mage.util.CardUtil;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if the spell was cast with the alternate Bargain cost
|
* Checks if the spell was cast with the alternate Bargain cost
|
||||||
|
|
@ -18,16 +17,7 @@ public enum BargainedCondition implements Condition {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean apply(Game game, Ability source) {
|
public boolean apply(Game game, Ability source) {
|
||||||
// TODO: replace by Tag Cost Tracking.
|
return CardUtil.checkSourceCostsTagExists(game, source, BargainAbility.BARGAIN_ACTIVATION_VALUE_KEY);
|
||||||
MageObject sourceObject = source.getSourceObject(game);
|
|
||||||
if (sourceObject instanceof Card) {
|
|
||||||
for (Ability ability : ((Card) sourceObject).getAbilities(game)) {
|
|
||||||
if (ability instanceof BargainAbility) {
|
|
||||||
return ((BargainAbility) ability).wasBargained(game, source);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,7 @@ import mage.abilities.Ability;
|
||||||
import mage.abilities.condition.Condition;
|
import mage.abilities.condition.Condition;
|
||||||
import mage.abilities.keyword.BlitzAbility;
|
import mage.abilities.keyword.BlitzAbility;
|
||||||
import mage.game.Game;
|
import mage.game.Game;
|
||||||
|
import mage.util.CardUtil;
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author TheElk801
|
* @author TheElk801
|
||||||
|
|
@ -15,7 +14,6 @@ public enum BlitzedCondition implements Condition {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean apply(Game game, Ability source) {
|
public boolean apply(Game game, Ability source) {
|
||||||
List<Integer> blitzActivations = (List<Integer>) game.getState().getValue(BlitzAbility.BLITZ_ACTIVATION_VALUE_KEY + source.getSourceId());
|
return CardUtil.checkSourceCostsTagExists(game, source, BlitzAbility.BLITZ_ACTIVATION_VALUE_KEY);
|
||||||
return blitzActivations != null && blitzActivations.contains(game.getState().getZoneChangeCounter(source.getSourceId()));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@ package mage.abilities.condition.common;
|
||||||
import mage.abilities.Ability;
|
import mage.abilities.Ability;
|
||||||
import mage.abilities.condition.Condition;
|
import mage.abilities.condition.Condition;
|
||||||
import mage.abilities.keyword.DashAbility;
|
import mage.abilities.keyword.DashAbility;
|
||||||
import mage.cards.Card;
|
|
||||||
import mage.game.Game;
|
import mage.game.Game;
|
||||||
import mage.util.CardUtil;
|
import mage.util.CardUtil;
|
||||||
|
|
||||||
|
|
@ -15,11 +14,6 @@ public enum DashedCondition implements Condition {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean apply(Game game, Ability source) {
|
public boolean apply(Game game, Ability source) {
|
||||||
Card card = game.getCard(source.getSourceId());
|
return CardUtil.checkSourceCostsTagExists(game, source, DashAbility.getActivationKey());
|
||||||
return card != null
|
|
||||||
&& CardUtil.castStream(card
|
|
||||||
.getAbilities(game)
|
|
||||||
.stream(), DashAbility.class)
|
|
||||||
.anyMatch(ability -> ability.isActivated(source, game));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,8 @@ package mage.abilities.condition.common;
|
||||||
import mage.abilities.Ability;
|
import mage.abilities.Ability;
|
||||||
import mage.abilities.condition.Condition;
|
import mage.abilities.condition.Condition;
|
||||||
import mage.abilities.keyword.EvokeAbility;
|
import mage.abilities.keyword.EvokeAbility;
|
||||||
import mage.cards.Card;
|
|
||||||
import mage.game.Game;
|
import mage.game.Game;
|
||||||
|
import mage.util.CardUtil;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if a the spell was cast with the alternate evoke costs
|
* Checks if a the spell was cast with the alternate evoke costs
|
||||||
|
|
@ -20,12 +20,6 @@ public enum EvokedCondition implements Condition {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean apply(Game game, Ability source) {
|
public boolean apply(Game game, Ability source) {
|
||||||
Card card = game.getCard(source.getSourceId());
|
return CardUtil.checkSourceCostsTagExists(game, source, EvokeAbility.getActivationKey());
|
||||||
if (card != null) {
|
|
||||||
return card.getAbilities(game).stream()
|
|
||||||
.filter(EvokeAbility.class::isInstance)
|
|
||||||
.anyMatch(evoke -> ((EvokeAbility) evoke).isActivated(source, game));
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ public enum KickedCondition implements Condition {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean apply(Game game, Ability source) {
|
public boolean apply(Game game, Ability source) {
|
||||||
return KickerAbility.getSourceObjectKickedCount(game, source) >= kickedCount;
|
return KickerAbility.getKickedCounter(game, source) >= kickedCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,8 @@
|
||||||
package mage.abilities.condition.common;
|
package mage.abilities.condition.common;
|
||||||
|
|
||||||
import mage.MageObject;
|
|
||||||
import mage.abilities.Ability;
|
import mage.abilities.Ability;
|
||||||
import mage.abilities.condition.Condition;
|
import mage.abilities.condition.Condition;
|
||||||
import mage.abilities.keyword.KickerAbility;
|
import mage.abilities.keyword.KickerAbility;
|
||||||
import mage.cards.Card;
|
|
||||||
import mage.game.Game;
|
import mage.game.Game;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -22,14 +20,6 @@ public class KickedCostCondition implements Condition {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean apply(Game game, Ability source) {
|
public boolean apply(Game game, Ability source) {
|
||||||
MageObject sourceObject = source.getSourceObject(game);
|
return KickerAbility.getKickedCounterStrict(game, source, kickerCostText) > 0;
|
||||||
if (sourceObject instanceof Card) {
|
|
||||||
for (Ability ability : ((Card) sourceObject).getAbilities(game)) {
|
|
||||||
if (ability instanceof KickerAbility) {
|
|
||||||
return ((KickerAbility) ability).isKicked(game, source, kickerCostText);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,8 @@ package mage.abilities.condition.common;
|
||||||
import mage.abilities.Ability;
|
import mage.abilities.Ability;
|
||||||
import mage.abilities.condition.Condition;
|
import mage.abilities.condition.Condition;
|
||||||
import mage.abilities.keyword.ProwlAbility;
|
import mage.abilities.keyword.ProwlAbility;
|
||||||
import mage.cards.Card;
|
|
||||||
import mage.game.Game;
|
import mage.game.Game;
|
||||||
|
import mage.util.CardUtil;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if a the spell was cast with the alternate prowl costs
|
* Checks if a the spell was cast with the alternate prowl costs
|
||||||
|
|
@ -17,17 +17,7 @@ public enum ProwlCostWasPaidCondition implements Condition {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean apply(Game game, Ability source) {
|
public boolean apply(Game game, Ability source) {
|
||||||
Card card = game.getCard(source.getSourceId());
|
return CardUtil.checkSourceCostsTagExists(game, source, ProwlAbility.getActivationKey());
|
||||||
if (card != null) {
|
|
||||||
for (Ability ability : card.getAbilities(game)) {
|
|
||||||
if (ability instanceof ProwlAbility) {
|
|
||||||
if (((ProwlAbility) ability).isActivated(source, game)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
||||||
|
|
@ -4,11 +4,8 @@ package mage.abilities.condition.common;
|
||||||
import mage.abilities.Ability;
|
import mage.abilities.Ability;
|
||||||
import mage.abilities.condition.Condition;
|
import mage.abilities.condition.Condition;
|
||||||
import mage.abilities.keyword.SpectacleAbility;
|
import mage.abilities.keyword.SpectacleAbility;
|
||||||
import mage.constants.AbilityType;
|
|
||||||
import mage.game.Game;
|
import mage.game.Game;
|
||||||
|
import mage.util.CardUtil;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author TheElk801
|
* @author TheElk801
|
||||||
|
|
@ -19,15 +16,6 @@ public enum SpectacleCondition implements Condition {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean apply(Game game, Ability source) {
|
public boolean apply(Game game, Ability source) {
|
||||||
if (source.getAbilityType() == AbilityType.TRIGGERED) {
|
return CardUtil.checkSourceCostsTagExists(game, source, SpectacleAbility.SPECTACLE_ACTIVATION_VALUE_KEY);
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
List<Integer> spectacleActivations = (ArrayList) game.getState().getValue(SpectacleAbility.SPECTACLE_ACTIVATION_VALUE_KEY + source.getSourceId());
|
|
||||||
if (spectacleActivations != null) {
|
|
||||||
return spectacleActivations.contains(game.getState().getZoneChangeCounter(source.getSourceId()) - 1);
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
} else {
|
|
||||||
return source instanceof SpectacleAbility;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,11 +4,8 @@ package mage.abilities.condition.common;
|
||||||
import mage.abilities.Ability;
|
import mage.abilities.Ability;
|
||||||
import mage.abilities.condition.Condition;
|
import mage.abilities.condition.Condition;
|
||||||
import mage.abilities.keyword.SurgeAbility;
|
import mage.abilities.keyword.SurgeAbility;
|
||||||
import mage.constants.AbilityType;
|
|
||||||
import mage.game.Game;
|
import mage.game.Game;
|
||||||
|
import mage.util.CardUtil;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
|
|
@ -20,15 +17,6 @@ public enum SurgedCondition implements Condition {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean apply(Game game, Ability source) {
|
public boolean apply(Game game, Ability source) {
|
||||||
if (source.getAbilityType() == AbilityType.TRIGGERED) {
|
return CardUtil.checkSourceCostsTagExists(game, source, SurgeAbility.SURGE_ACTIVATION_VALUE_KEY);
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
List<Integer> surgeActivations = (ArrayList) game.getState().getValue(SurgeAbility.SURGE_ACTIVATION_VALUE_KEY + source.getSourceId());
|
|
||||||
if (surgeActivations != null) {
|
|
||||||
return surgeActivations.contains(game.getState().getZoneChangeCounter(source.getSourceId()) - 1);
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
} else {
|
|
||||||
return source instanceof SurgeAbility;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,11 +5,11 @@ import mage.abilities.SpellAbility;
|
||||||
import mage.abilities.StaticAbility;
|
import mage.abilities.StaticAbility;
|
||||||
import mage.abilities.costs.mana.ManaCost;
|
import mage.abilities.costs.mana.ManaCost;
|
||||||
import mage.abilities.costs.mana.ManaCostsImpl;
|
import mage.abilities.costs.mana.ManaCostsImpl;
|
||||||
import mage.cards.Card;
|
|
||||||
import mage.constants.Outcome;
|
import mage.constants.Outcome;
|
||||||
import mage.constants.Zone;
|
import mage.constants.Zone;
|
||||||
import mage.game.Game;
|
import mage.game.Game;
|
||||||
import mage.players.Player;
|
import mage.players.Player;
|
||||||
|
import mage.util.CardUtil;
|
||||||
|
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
|
|
||||||
|
|
@ -20,7 +20,10 @@ public abstract class AlternativeSourceCostsImpl extends StaticAbility implement
|
||||||
|
|
||||||
protected final AlternativeCost alternativeCost;
|
protected final AlternativeCost alternativeCost;
|
||||||
protected final String reminderText;
|
protected final String reminderText;
|
||||||
private int zoneChangeCounter = 0;
|
protected final String activationKey;
|
||||||
|
protected static String getActivationKey(String name){
|
||||||
|
return name+"ActivationKey";
|
||||||
|
}
|
||||||
|
|
||||||
protected AlternativeSourceCostsImpl(String name, String reminderText, String manaString) {
|
protected AlternativeSourceCostsImpl(String name, String reminderText, String manaString) {
|
||||||
this(name, reminderText, new ManaCostsImpl<>(manaString));
|
this(name, reminderText, new ManaCostsImpl<>(manaString));
|
||||||
|
|
@ -31,13 +34,14 @@ public abstract class AlternativeSourceCostsImpl extends StaticAbility implement
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.reminderText = reminderText;
|
this.reminderText = reminderText;
|
||||||
this.alternativeCost = new AlternativeCostImpl<>(name, reminderText, cost);
|
this.alternativeCost = new AlternativeCostImpl<>(name, reminderText, cost);
|
||||||
|
this.activationKey = getActivationKey(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected AlternativeSourceCostsImpl(final AlternativeSourceCostsImpl ability) {
|
protected AlternativeSourceCostsImpl(final AlternativeSourceCostsImpl ability) {
|
||||||
super(ability);
|
super(ability);
|
||||||
this.alternativeCost = ability.alternativeCost.copy();
|
this.alternativeCost = ability.alternativeCost.copy();
|
||||||
this.reminderText = ability.reminderText;
|
this.reminderText = ability.reminderText;
|
||||||
this.zoneChangeCounter = ability.zoneChangeCounter;
|
this.activationKey = ability.activationKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -58,15 +62,9 @@ public abstract class AlternativeSourceCostsImpl extends StaticAbility implement
|
||||||
|| !player.chooseUse(Outcome.Benefit, "Cast this for its " + this.name + " cost? (" + alternativeCost.getText(true) + ')', ability, game)) {
|
|| !player.chooseUse(Outcome.Benefit, "Cast this for its " + this.name + " cost? (" + alternativeCost.getText(true) + ')', ability, game)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
ability.setCostsTag(activationKey, null);
|
||||||
alternativeCost.activate();
|
alternativeCost.activate();
|
||||||
if (zoneChangeCounter == 0) {
|
|
||||||
Card card = game.getCard(getSourceId());
|
|
||||||
if (card != null) {
|
|
||||||
zoneChangeCounter = card.getZoneChangeCounter(game);
|
|
||||||
} else {
|
|
||||||
throw new IllegalArgumentException("source card not found");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ability.clearManaCostsToPay();
|
ability.clearManaCostsToPay();
|
||||||
ability.clearCosts();
|
ability.clearCosts();
|
||||||
for (Iterator<Cost> it = ((Costs<Cost>) alternativeCost).iterator(); it.hasNext(); ) {
|
for (Iterator<Cost> it = ((Costs<Cost>) alternativeCost).iterator(); it.hasNext(); ) {
|
||||||
|
|
@ -82,11 +80,7 @@ public abstract class AlternativeSourceCostsImpl extends StaticAbility implement
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isActivated(Ability ability, Game game) {
|
public boolean isActivated(Ability ability, Game game) {
|
||||||
Card card = game.getCard(sourceId);
|
return CardUtil.checkSourceCostsTagExists(game, ability, activationKey);
|
||||||
if (card != null && card.getZoneChangeCounter(game) <= zoneChangeCounter + 1) {
|
|
||||||
return alternativeCost.isActivated(game);
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -102,7 +96,6 @@ public abstract class AlternativeSourceCostsImpl extends StaticAbility implement
|
||||||
@Override
|
@Override
|
||||||
public void resetCost() {
|
public void resetCost() {
|
||||||
alternativeCost.reset();
|
alternativeCost.reset();
|
||||||
this.zoneChangeCounter = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ public enum GetKickerXValue implements DynamicValue {
|
||||||
public int calculate(Game game, Ability sourceAbility, Effect effect) {
|
public int calculate(Game game, Ability sourceAbility, Effect effect) {
|
||||||
// Currently identical logic to the Manacost X value
|
// Currently identical logic to the Manacost X value
|
||||||
// which should be fine since you can only have one X at a time
|
// which should be fine since you can only have one X at a time
|
||||||
return (int) CardUtil.getSourceCostsTag(game, sourceAbility, "X", 0);
|
return CardUtil.getSourceCostsTag(game, sourceAbility, "X", 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ public enum GetXLoyaltyValue implements DynamicValue {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int calculate(Game game, Ability sourceAbility, Effect effect) {
|
public int calculate(Game game, Ability sourceAbility, Effect effect) {
|
||||||
return (int) CardUtil.getSourceCostsTag(game, sourceAbility, "X", 0);
|
return CardUtil.getSourceCostsTag(game, sourceAbility, "X", 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ public enum GetXValue implements DynamicValue {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int calculate(Game game, Ability sourceAbility, Effect effect) {
|
public int calculate(Game game, Ability sourceAbility, Effect effect) {
|
||||||
return (int) CardUtil.getSourceCostsTag(game, sourceAbility, "X", 0);
|
return CardUtil.getSourceCostsTag(game, sourceAbility, "X", 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ public enum ManacostVariableValue implements DynamicValue {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int calculate(Game game, Ability sourceAbility, Effect effect) {
|
public int calculate(Game game, Ability sourceAbility, Effect effect) {
|
||||||
return (int) CardUtil.getSourceCostsTag(game, sourceAbility, "X", 0);
|
return CardUtil.getSourceCostsTag(game, sourceAbility, "X", 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ public enum MultikickerCount implements DynamicValue {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int calculate(Game game, Ability sourceAbility, Effect effect) {
|
public int calculate(Game game, Ability sourceAbility, Effect effect) {
|
||||||
return KickerAbility.getSourceObjectKickedCount(game, sourceAbility);
|
return KickerAbility.getKickedCounter(game, sourceAbility);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
||||||
|
|
@ -57,7 +57,7 @@ public class EntersBattlefieldWithXCountersEffect extends OneShotEffect {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (permanent != null) {
|
if (permanent != null) {
|
||||||
int amount = ((int) CardUtil.getSourceCostsTag(game, source, "X", 0)) * multiplier;
|
int amount = CardUtil.getSourceCostsTag(game, source, "X", 0) * multiplier;
|
||||||
if (amount > 0) {
|
if (amount > 0) {
|
||||||
Counter counterToAdd = counter.copy();
|
Counter counterToAdd = counter.copy();
|
||||||
counterToAdd.add(amount - counter.getCount());
|
counterToAdd.add(amount - counter.getCount());
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
|
|
||||||
package mage.abilities.keyword;
|
package mage.abilities.keyword;
|
||||||
|
|
||||||
import mage.MageObject;
|
|
||||||
import mage.abilities.Ability;
|
import mage.abilities.Ability;
|
||||||
import mage.abilities.SpellAbility;
|
import mage.abilities.SpellAbility;
|
||||||
import mage.abilities.StaticAbility;
|
import mage.abilities.StaticAbility;
|
||||||
|
|
@ -16,7 +15,6 @@ import mage.filter.predicate.Predicates;
|
||||||
import mage.filter.predicate.permanent.TokenPredicate;
|
import mage.filter.predicate.permanent.TokenPredicate;
|
||||||
import mage.game.Game;
|
import mage.game.Game;
|
||||||
import mage.players.Player;
|
import mage.players.Player;
|
||||||
import mage.util.CardUtil;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Written before ruling was clarified. Feel free to put the ruling once it gets there.
|
* Written before ruling was clarified. Feel free to put the ruling once it gets there.
|
||||||
|
|
@ -37,7 +35,7 @@ public class BargainAbility extends StaticAbility implements OptionalAdditionalS
|
||||||
private static final String reminderText = "You may sacrifice an artifact, enchantment, or token as you cast this spell.";
|
private static final String reminderText = "You may sacrifice an artifact, enchantment, or token as you cast this spell.";
|
||||||
private final String rule;
|
private final String rule;
|
||||||
|
|
||||||
private String activationKey; // TODO: replace by Tag Cost Tracking.
|
public static final String BARGAIN_ACTIVATION_VALUE_KEY = "bargainActivation";
|
||||||
|
|
||||||
protected OptionalAdditionalCost additionalCost;
|
protected OptionalAdditionalCost additionalCost;
|
||||||
|
|
||||||
|
|
@ -61,14 +59,12 @@ public class BargainAbility extends StaticAbility implements OptionalAdditionalS
|
||||||
this.rule = additionalCost.getName() + ' ' + additionalCost.getReminderText();
|
this.rule = additionalCost.getName() + ' ' + additionalCost.getReminderText();
|
||||||
this.setRuleAtTheTop(true);
|
this.setRuleAtTheTop(true);
|
||||||
this.addHint(BargainCostWasPaidHint.instance);
|
this.addHint(BargainCostWasPaidHint.instance);
|
||||||
this.activationKey = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private BargainAbility(final BargainAbility ability) {
|
private BargainAbility(final BargainAbility ability) {
|
||||||
super(ability);
|
super(ability);
|
||||||
this.rule = ability.rule;
|
this.rule = ability.rule;
|
||||||
this.additionalCost = ability.additionalCost.copy();
|
this.additionalCost = ability.additionalCost.copy();
|
||||||
this.activationKey = ability.activationKey;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -80,7 +76,6 @@ public class BargainAbility extends StaticAbility implements OptionalAdditionalS
|
||||||
if (additionalCost != null) {
|
if (additionalCost != null) {
|
||||||
additionalCost.reset();
|
additionalCost.reset();
|
||||||
}
|
}
|
||||||
this.activationKey = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -104,7 +99,7 @@ public class BargainAbility extends StaticAbility implements OptionalAdditionalS
|
||||||
for (Cost cost : ((Costs<Cost>) additionalCost)) {
|
for (Cost cost : ((Costs<Cost>) additionalCost)) {
|
||||||
ability.getCosts().add(cost.copy());
|
ability.getCosts().add(cost.copy());
|
||||||
}
|
}
|
||||||
this.activationKey = getActivationKey(ability, game);
|
ability.setCostsTag(BARGAIN_ACTIVATION_VALUE_KEY, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -112,40 +107,6 @@ public class BargainAbility extends StaticAbility implements OptionalAdditionalS
|
||||||
return additionalCost.getCastSuffixMessage(0);
|
return additionalCost.getCastSuffixMessage(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean wasBargained(Game game, Ability source) {
|
|
||||||
return activationKey != null && getActivationKey(source, game).equalsIgnoreCase(activationKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* TODO: remove with Tag Cost Tracking.
|
|
||||||
* Return activation zcc key for searching spell's settings in source object
|
|
||||||
*
|
|
||||||
* @param source
|
|
||||||
* @param game
|
|
||||||
*/
|
|
||||||
public static String getActivationKey(Ability source, Game game) {
|
|
||||||
// Bargain activates in STACK zone so all zcc must be from "stack moment"
|
|
||||||
// Use cases:
|
|
||||||
// * resolving spell have same zcc (example: check kicker status in sorcery/instant);
|
|
||||||
// * copied spell have same zcc as source spell (see Spell.copySpell and zcc sync);
|
|
||||||
// * creature/token from resolved spell have +1 zcc after moved to battlefield (example: check kicker status in ETB triggers/effects);
|
|
||||||
|
|
||||||
// find object info from the source ability (it can be a permanent or a spell on stack, on the moment of trigger/resolve)
|
|
||||||
MageObject sourceObject = source.getSourceObject(game);
|
|
||||||
Zone sourceObjectZone = game.getState().getZone(sourceObject.getId());
|
|
||||||
int zcc = CardUtil.getActualSourceObjectZoneChangeCounter(game, source);
|
|
||||||
|
|
||||||
// find "stack moment" zcc:
|
|
||||||
// * permanent cards enters from STACK to BATTLEFIELD (+1 zcc)
|
|
||||||
// * permanent tokens enters from OUTSIDE to BATTLEFIELD (+1 zcc, see prepare code in TokenImpl.putOntoBattlefieldHelper)
|
|
||||||
// * spells and copied spells resolves on STACK (zcc not changes)
|
|
||||||
if (sourceObjectZone != Zone.STACK) {
|
|
||||||
--zcc;
|
|
||||||
}
|
|
||||||
|
|
||||||
return zcc + "";
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getRule() {
|
public String getRule() {
|
||||||
return rule;
|
return rule;
|
||||||
|
|
|
||||||
|
|
@ -19,9 +19,6 @@ import mage.constants.TimingRule;
|
||||||
import mage.game.Game;
|
import mage.game.Game;
|
||||||
import mage.target.targetpointer.FixedTarget;
|
import mage.target.targetpointer.FixedTarget;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author TheElk801
|
* @author TheElk801
|
||||||
*/
|
*/
|
||||||
|
|
@ -80,15 +77,7 @@ public class BlitzAbility extends SpellAbility {
|
||||||
if (!super.activate(game, noMana)) {
|
if (!super.activate(game, noMana)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
Object obj = game.getState().getValue(BLITZ_ACTIVATION_VALUE_KEY + getSourceId());
|
this.setCostsTag(BLITZ_ACTIVATION_VALUE_KEY, null);
|
||||||
List<Integer> blitzActivations;
|
|
||||||
if (obj != null) {
|
|
||||||
blitzActivations = (List<Integer>) obj;
|
|
||||||
} else {
|
|
||||||
blitzActivations = new ArrayList<>();
|
|
||||||
game.getState().setValue(BLITZ_ACTIVATION_VALUE_KEY + getSourceId(), blitzActivations);
|
|
||||||
}
|
|
||||||
blitzActivations.add(game.getState().getZoneChangeCounter(getSourceId()));
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -41,6 +41,9 @@ public class DashAbility extends AlternativeSourceCostsImpl {
|
||||||
public DashAbility copy() {
|
public DashAbility copy() {
|
||||||
return new DashAbility(this);
|
return new DashAbility(this);
|
||||||
}
|
}
|
||||||
|
public static String getActivationKey(){
|
||||||
|
return getActivationKey(KEYWORD);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class DashAddDelayedTriggeredAbilityEffect extends OneShotEffect {
|
class DashAddDelayedTriggeredAbilityEffect extends OneShotEffect {
|
||||||
|
|
|
||||||
|
|
@ -9,10 +9,9 @@ import mage.constants.Outcome;
|
||||||
import mage.constants.Zone;
|
import mage.constants.Zone;
|
||||||
import mage.game.Game;
|
import mage.game.Game;
|
||||||
import mage.players.Player;
|
import mage.players.Player;
|
||||||
|
import mage.util.CardUtil;
|
||||||
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 702.40. Entwine
|
* 702.40. Entwine
|
||||||
|
|
@ -32,9 +31,9 @@ public class EntwineAbility extends StaticAbility implements OptionalAdditionalM
|
||||||
|
|
||||||
private static final String keywordText = "Entwine";
|
private static final String keywordText = "Entwine";
|
||||||
protected static final String reminderText = "You may {cost} in addition to any other costs to use all modes.";
|
protected static final String reminderText = "You may {cost} in addition to any other costs to use all modes.";
|
||||||
|
protected static final String ENTWINE_ACTIVATION_VALUE_KEY = "entwineActivation";
|
||||||
|
|
||||||
protected OptionalAdditionalCost entwineCost;
|
protected OptionalAdditionalCost entwineCost;
|
||||||
protected Set<String> activations = new HashSet<>(); // same logic as KickerAbility: activations per zoneChangeCounter
|
|
||||||
|
|
||||||
public EntwineAbility(String manaString) {
|
public EntwineAbility(String manaString) {
|
||||||
super(Zone.STACK, null);
|
super(Zone.STACK, null);
|
||||||
|
|
@ -62,7 +61,6 @@ public class EntwineAbility extends StaticAbility implements OptionalAdditionalM
|
||||||
if (ability.entwineCost != null) {
|
if (ability.entwineCost != null) {
|
||||||
this.entwineCost = ability.entwineCost.copy();
|
this.entwineCost = ability.entwineCost.copy();
|
||||||
}
|
}
|
||||||
this.activations.addAll(ability.activations);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -97,7 +95,7 @@ public class EntwineAbility extends StaticAbility implements OptionalAdditionalM
|
||||||
ability.addCost(cost.copy());
|
ability.addCost(cost.copy());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
activateEntwine(game, ability);
|
ability.setCostsTag(ENTWINE_ACTIVATION_VALUE_KEY, null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -135,23 +133,9 @@ public class EntwineAbility extends StaticAbility implements OptionalAdditionalM
|
||||||
if (entwineCost != null) {
|
if (entwineCost != null) {
|
||||||
entwineCost.reset();
|
entwineCost.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
String key = getActivationKey(source, game);
|
|
||||||
this.activations.remove(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void activateEntwine(Game game, Ability source) {
|
|
||||||
String key = getActivationKey(source, game);
|
|
||||||
this.activations.add(key);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean costWasActivated(Ability ability, Game game) {
|
public boolean costWasActivated(Ability ability, Game game) {
|
||||||
String key = getActivationKey(ability, game);
|
return CardUtil.checkSourceCostsTagExists(game, ability, ENTWINE_ACTIVATION_VALUE_KEY);
|
||||||
return this.activations.contains(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
private String getActivationKey(Ability source, Game game) {
|
|
||||||
// same logic as KickerAbility
|
|
||||||
return KickerAbility.getActivationKey(source, game);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -39,4 +39,8 @@ public class EvokeAbility extends AlternativeSourceCostsImpl {
|
||||||
public EvokeAbility copy() {
|
public EvokeAbility copy() {
|
||||||
return new EvokeAbility(this);
|
return new EvokeAbility(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static String getActivationKey(){
|
||||||
|
return getActivationKey(EVOKE_KEYWORD);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,10 @@
|
||||||
package mage.abilities.keyword;
|
package mage.abilities.keyword;
|
||||||
|
|
||||||
import mage.MageObject;
|
|
||||||
import mage.abilities.Ability;
|
import mage.abilities.Ability;
|
||||||
import mage.abilities.SpellAbility;
|
import mage.abilities.SpellAbility;
|
||||||
import mage.abilities.StaticAbility;
|
import mage.abilities.StaticAbility;
|
||||||
import mage.abilities.costs.*;
|
import mage.abilities.costs.*;
|
||||||
import mage.abilities.costs.mana.ManaCostsImpl;
|
import mage.abilities.costs.mana.ManaCostsImpl;
|
||||||
import mage.cards.Card;
|
|
||||||
import mage.constants.Outcome;
|
import mage.constants.Outcome;
|
||||||
import mage.constants.Zone;
|
import mage.constants.Zone;
|
||||||
import mage.game.Game;
|
import mage.game.Game;
|
||||||
|
|
@ -16,7 +14,7 @@ import mage.players.Player;
|
||||||
import mage.util.CardUtil;
|
import mage.util.CardUtil;
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 20121001 702.31. Kicker 702.31a Kicker is a static ability that functions
|
* 20121001 702.31. Kicker 702.31a Kicker is a static ability that functions
|
||||||
|
|
@ -55,8 +53,6 @@ public class KickerAbility extends StaticAbility implements OptionalAdditionalSo
|
||||||
protected static final String KICKER_REMINDER_COST = "You may {cost} in addition "
|
protected static final String KICKER_REMINDER_COST = "You may {cost} in addition "
|
||||||
+ "to any other costs as you cast this spell.";
|
+ "to any other costs as you cast this spell.";
|
||||||
|
|
||||||
protected Map<String, Integer> activations = new ConcurrentHashMap<>(); // zoneChangeCounter, activations
|
|
||||||
|
|
||||||
protected String keywordText;
|
protected String keywordText;
|
||||||
protected String reminderText;
|
protected String reminderText;
|
||||||
protected List<OptionalAdditionalCost> kickerCosts = new LinkedList<>();
|
protected List<OptionalAdditionalCost> kickerCosts = new LinkedList<>();
|
||||||
|
|
@ -86,7 +82,6 @@ public class KickerAbility extends StaticAbility implements OptionalAdditionalSo
|
||||||
}
|
}
|
||||||
this.keywordText = ability.keywordText;
|
this.keywordText = ability.keywordText;
|
||||||
this.reminderText = ability.reminderText;
|
this.reminderText = ability.reminderText;
|
||||||
this.activations.putAll(ability.activations);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -119,32 +114,36 @@ public class KickerAbility extends StaticAbility implements OptionalAdditionalSo
|
||||||
for (OptionalAdditionalCost cost : kickerCosts) {
|
for (OptionalAdditionalCost cost : kickerCosts) {
|
||||||
cost.reset();
|
cost.reset();
|
||||||
}
|
}
|
||||||
activations.clear();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private int getKickedCounterStrict(Game game, Ability source, String needKickerCost) {
|
private static String getActivationKey(String needKickerCost){
|
||||||
String key;
|
return "kickerActivation"+needKickerCost;
|
||||||
if (needKickerCost.isEmpty()) {
|
}
|
||||||
// need all kickers
|
|
||||||
key = getActivationKey(source, "", game);
|
|
||||||
} else {
|
|
||||||
// need only cost related kickers
|
|
||||||
key = getActivationKey(source, needKickerCost, game);
|
|
||||||
}
|
|
||||||
|
|
||||||
int totalActivations = 0;
|
/**
|
||||||
if (kickerCosts.size() > 1) {
|
* Return total kicker activations with the specified Cost (blank for all kickers/multikickers)
|
||||||
for (String activationKey : activations.keySet()) {
|
* Checks the start of the tags, to work for that blank method, which requires direct access
|
||||||
if (activationKey.startsWith(key) && activations.get(activationKey) > 0) {
|
*
|
||||||
totalActivations += activations.get(activationKey);
|
* @param game
|
||||||
}
|
* @param source
|
||||||
}
|
* @param needKickerCost use cost.getText(true)
|
||||||
} else {
|
* @return
|
||||||
if (activations.containsKey(key) && activations.get(key) > 0) {
|
*/
|
||||||
totalActivations += activations.get(key);
|
public static int getKickedCounterStrict(Game game, Ability source, String needKickerCost) {
|
||||||
}
|
Map<String, Object> costsTags = CardUtil.getSourceCostsTagsMap(game, source);
|
||||||
|
if (costsTags == null) {
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
return totalActivations;
|
String finalActivationKey = getActivationKey(needKickerCost);
|
||||||
|
Stream<Map.Entry<String, Object>> tagStream = costsTags.entrySet().stream()
|
||||||
|
.filter(x -> x.getKey().startsWith(finalActivationKey));
|
||||||
|
return tagStream.mapToInt(x -> {
|
||||||
|
Object value = x.getValue();
|
||||||
|
if (!(value instanceof Integer)){
|
||||||
|
throw new IllegalStateException("Wrong code usage: Kicker tag "+x.getKey()+" needs Integer but has "+(value==null?"null":value.getClass().getName()));
|
||||||
|
}
|
||||||
|
return (int) value;
|
||||||
|
}).sum();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -154,7 +153,7 @@ public class KickerAbility extends StaticAbility implements OptionalAdditionalSo
|
||||||
* @param source
|
* @param source
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
public int getKickedCounter(Game game, Ability source) {
|
public static int getKickedCounter(Game game, Ability source) {
|
||||||
return getKickedCounterStrict(game, source, "");
|
return getKickedCounterStrict(game, source, "");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -186,47 +185,11 @@ public class KickerAbility extends StaticAbility implements OptionalAdditionalSo
|
||||||
}
|
}
|
||||||
|
|
||||||
private void activateKicker(OptionalAdditionalCost kickerCost, Ability source, Game game) {
|
private void activateKicker(OptionalAdditionalCost kickerCost, Ability source, Game game) {
|
||||||
int amount = 1;
|
|
||||||
String key = getActivationKey(source, kickerCost.getText(true), game);
|
|
||||||
if (activations.containsKey(key)) {
|
|
||||||
amount += activations.get(key);
|
|
||||||
}
|
|
||||||
activations.put(key, amount);
|
|
||||||
game.fireEvent(GameEvent.getEvent(GameEvent.EventType.KICKED, source.getSourceId(), source, source.getControllerId()));
|
game.fireEvent(GameEvent.getEvent(GameEvent.EventType.KICKED, source.getSourceId(), source, source.getControllerId()));
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
String activationKey = getActivationKey(kickerCost.getText(true));
|
||||||
* Return activation zcc key for searching spell's settings in source object
|
Integer next = CardUtil.getSourceCostsTag(game, source, activationKey,0)+1;
|
||||||
*
|
source.setCostsTag(activationKey,next);
|
||||||
* @param source
|
|
||||||
* @param game
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public static String getActivationKey(Ability source, Game game) {
|
|
||||||
// Kicker activates in STACK zone so all zcc must be from "stack moment"
|
|
||||||
// Use cases:
|
|
||||||
// * resolving spell have same zcc (example: check kicker status in sorcery/instant);
|
|
||||||
// * copied spell have same zcc as source spell (see Spell.copySpell and zcc sync);
|
|
||||||
// * creature/token from resolved spell have +1 zcc after moved to battlefield (example: check kicker status in ETB triggers/effects);
|
|
||||||
|
|
||||||
// find object info from the source ability (it can be a permanent or a spell on stack, on the moment of trigger/resolve)
|
|
||||||
MageObject sourceObject = source.getSourceObject(game);
|
|
||||||
Zone sourceObjectZone = game.getState().getZone(sourceObject.getId());
|
|
||||||
int zcc = CardUtil.getActualSourceObjectZoneChangeCounter(game, source);
|
|
||||||
|
|
||||||
// find "stack moment" zcc:
|
|
||||||
// * permanent cards enters from STACK to BATTLEFIELD (+1 zcc)
|
|
||||||
// * permanent tokens enters from OUTSIDE to BATTLEFIELD (+1 zcc, see prepare code in TokenImpl.putOntoBattlefieldHelper)
|
|
||||||
// * spells and copied spells resolves on STACK (zcc not changes)
|
|
||||||
if (sourceObjectZone != Zone.STACK) {
|
|
||||||
--zcc;
|
|
||||||
}
|
|
||||||
|
|
||||||
return zcc + "";
|
|
||||||
}
|
|
||||||
|
|
||||||
private String getActivationKey(Ability source, String costText, Game game) {
|
|
||||||
return getActivationKey(source, game) + ((kickerCosts.size() > 1) ? costText : "");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -330,35 +293,10 @@ public class KickerAbility extends StaticAbility implements OptionalAdditionalSo
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
public static int getSpellKickedCount(Game game, UUID spellId) {
|
public static int getSpellKickedCount(Game game, UUID spellId) {
|
||||||
int count = 0;
|
|
||||||
Spell spell = game.getSpellOrLKIStack(spellId);
|
Spell spell = game.getSpellOrLKIStack(spellId);
|
||||||
if (spell != null) {
|
if (spell != null) {
|
||||||
for (Ability ability : spell.getAbilities(game)) {
|
return KickerAbility.getKickedCounter(game, spell.getSpellAbility());
|
||||||
if (ability instanceof KickerAbility) {
|
|
||||||
count += ((KickerAbility) ability).getKickedCounter(game, spell.getSpellAbility());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return count;
|
return 0;
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Find source object's kicked stats. Can be used in any places, e.g. in ETB effects
|
|
||||||
*
|
|
||||||
* @param game
|
|
||||||
* @param abilityToCheck
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public static int getSourceObjectKickedCount(Game game, Ability abilityToCheck) {
|
|
||||||
MageObject sourceObject = abilityToCheck.getSourceObject(game);
|
|
||||||
int count = 0;
|
|
||||||
if (sourceObject instanceof Card) {
|
|
||||||
for (Ability ability : ((Card) sourceObject).getAbilities(game)) {
|
|
||||||
if (ability instanceof KickerAbility) {
|
|
||||||
count += ((KickerAbility) ability).getKickedCounter(game, abilityToCheck);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return count;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -46,4 +46,8 @@ public class ProwlAbility extends AlternativeSourceCostsImpl {
|
||||||
public boolean isAvailable(Ability source, Game game) {
|
public boolean isAvailable(Ability source, Game game) {
|
||||||
return ProwlCondition.instance.apply(game, source);
|
return ProwlCondition.instance.apply(game, source);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static String getActivationKey(){
|
||||||
|
return getActivationKey(PROWL_KEYWORD);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,8 +10,6 @@ import mage.constants.SpellAbilityType;
|
||||||
import mage.constants.Zone;
|
import mage.constants.Zone;
|
||||||
import mage.game.Game;
|
import mage.game.Game;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -55,15 +53,9 @@ public class SpectacleAbility extends SpellAbility {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
public boolean activate(Game game, boolean noMana) {
|
public boolean activate(Game game, boolean noMana) {
|
||||||
if (super.activate(game, noMana)) {
|
if (super.activate(game, noMana)) {
|
||||||
List<Integer> spectacleActivations = (List<Integer>) game.getState().getValue(SPECTACLE_ACTIVATION_VALUE_KEY + getSourceId());
|
this.setCostsTag(SPECTACLE_ACTIVATION_VALUE_KEY,null);
|
||||||
if (spectacleActivations == null) {
|
|
||||||
spectacleActivations = new ArrayList<>(); // zoneChangeCounter
|
|
||||||
game.getState().setValue(SPECTACLE_ACTIVATION_VALUE_KEY + getSourceId(), spectacleActivations);
|
|
||||||
}
|
|
||||||
spectacleActivations.add(game.getState().getZoneChangeCounter(getSourceId()));
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
package mage.abilities.keyword;
|
package mage.abilities.keyword;
|
||||||
|
|
||||||
import mage.MageObject;
|
|
||||||
import mage.abilities.Ability;
|
import mage.abilities.Ability;
|
||||||
import mage.abilities.SpellAbility;
|
import mage.abilities.SpellAbility;
|
||||||
import mage.abilities.StaticAbility;
|
import mage.abilities.StaticAbility;
|
||||||
|
|
@ -9,7 +8,6 @@ import mage.abilities.costs.*;
|
||||||
import mage.abilities.costs.mana.GenericManaCost;
|
import mage.abilities.costs.mana.GenericManaCost;
|
||||||
import mage.abilities.costs.mana.ManaCostsImpl;
|
import mage.abilities.costs.mana.ManaCostsImpl;
|
||||||
import mage.abilities.effects.CreateTokenCopySourceEffect;
|
import mage.abilities.effects.CreateTokenCopySourceEffect;
|
||||||
import mage.abilities.effects.Effect;
|
|
||||||
import mage.abilities.effects.OneShotEffect;
|
import mage.abilities.effects.OneShotEffect;
|
||||||
import mage.constants.Outcome;
|
import mage.constants.Outcome;
|
||||||
import mage.constants.Zone;
|
import mage.constants.Zone;
|
||||||
|
|
@ -24,6 +22,7 @@ import mage.util.CardUtil;
|
||||||
public class SquadAbility extends StaticAbility implements OptionalAdditionalSourceCosts {
|
public class SquadAbility extends StaticAbility implements OptionalAdditionalSourceCosts {
|
||||||
protected OptionalAdditionalCost cost;
|
protected OptionalAdditionalCost cost;
|
||||||
protected static final String SQUAD_KEYWORD = "Squad";
|
protected static final String SQUAD_KEYWORD = "Squad";
|
||||||
|
protected static final String SQUAD_ACTIVATION_VALUE_KEY = "squadActivationCount";
|
||||||
protected static final String SQUAD_REMINDER = "You may pay an additional "
|
protected static final String SQUAD_REMINDER = "You may pay an additional "
|
||||||
+ "{cost} any number of times as you cast this spell.";
|
+ "{cost} any number of times as you cast this spell.";
|
||||||
public SquadAbility() {
|
public SquadAbility() {
|
||||||
|
|
@ -34,7 +33,6 @@ public class SquadAbility extends StaticAbility implements OptionalAdditionalSou
|
||||||
super(Zone.STACK, null);
|
super(Zone.STACK, null);
|
||||||
setSquadCost(cost);
|
setSquadCost(cost);
|
||||||
addSubAbility(new SquadTriggerAbility());
|
addSubAbility(new SquadTriggerAbility());
|
||||||
//Note that I get subabilities list's position 0 to modify the zcc/count references
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private SquadAbility(final SquadAbility ability) {
|
private SquadAbility(final SquadAbility ability) {
|
||||||
|
|
@ -47,7 +45,7 @@ public class SquadAbility extends StaticAbility implements OptionalAdditionalSou
|
||||||
return new SquadAbility(this);
|
return new SquadAbility(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public final void setSquadCost(Cost cost) {
|
private void setSquadCost(Cost cost) {
|
||||||
OptionalAdditionalCost newCost = new OptionalAdditionalCostImpl(
|
OptionalAdditionalCost newCost = new OptionalAdditionalCostImpl(
|
||||||
SQUAD_KEYWORD, SQUAD_REMINDER, cost);
|
SQUAD_KEYWORD, SQUAD_REMINDER, cost);
|
||||||
newCost.setRepeatable(true);
|
newCost.setRepeatable(true);
|
||||||
|
|
@ -59,28 +57,6 @@ public class SquadAbility extends StaticAbility implements OptionalAdditionalSou
|
||||||
cost.reset();
|
cost.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static int get_zcc(Ability source, Game game) {
|
|
||||||
// Squad/Kicker activates in STACK zone so all zcc must be from "stack moment"
|
|
||||||
// Use cases:
|
|
||||||
// * resolving spell have same zcc (example: check kicker status in sorcery/instant);
|
|
||||||
// * copied spell have same zcc as source spell (see Spell.copySpell and zcc sync);
|
|
||||||
// * creature/token from resolved spell have +1 zcc after moved to battlefield (example: check kicker status in ETB triggers/effects);
|
|
||||||
|
|
||||||
// find object info from the source ability (it can be a permanent or a spell on stack, on the moment of trigger/resolve)
|
|
||||||
MageObject sourceObject = source.getSourceObject(game);
|
|
||||||
Zone sourceObjectZone = game.getState().getZone(sourceObject.getId());
|
|
||||||
int zcc = CardUtil.getActualSourceObjectZoneChangeCounter(game, source);
|
|
||||||
|
|
||||||
// find "stack moment" zcc:
|
|
||||||
// * permanent cards enters from STACK to BATTLEFIELD (+1 zcc)
|
|
||||||
// * permanent tokens enters from OUTSIDE to BATTLEFIELD (+1 zcc, see prepare code in TokenImpl.putOntoBattlefieldHelper)
|
|
||||||
// * spells and copied spells resolves on STACK (zcc not changes)
|
|
||||||
if (sourceObjectZone != Zone.STACK) {
|
|
||||||
--zcc;
|
|
||||||
}
|
|
||||||
return zcc;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void addOptionalAdditionalCosts(Ability ability, Game game) {
|
public void addOptionalAdditionalCosts(Ability ability, Game game) {
|
||||||
if (!(ability instanceof SpellAbility)) {
|
if (!(ability instanceof SpellAbility)) {
|
||||||
|
|
@ -94,7 +70,7 @@ public class SquadAbility extends StaticAbility implements OptionalAdditionalSou
|
||||||
boolean again = true;
|
boolean again = true;
|
||||||
while (player.canRespond() && again) {
|
while (player.canRespond() && again) {
|
||||||
String times = "";
|
String times = "";
|
||||||
int activatedCount = getSquadCount();
|
int activatedCount = cost.getActivateCount();
|
||||||
times = (activatedCount + 1) + (activatedCount == 0 ? " time " : " times ");
|
times = (activatedCount + 1) + (activatedCount == 0 ? " time " : " times ");
|
||||||
// TODO: add AI support to find max number of possible activations (from available mana)
|
// TODO: add AI support to find max number of possible activations (from available mana)
|
||||||
// canPay checks only single mana available, not total mana usage
|
// canPay checks only single mana available, not total mana usage
|
||||||
|
|
@ -111,10 +87,7 @@ public class SquadAbility extends StaticAbility implements OptionalAdditionalSou
|
||||||
again = false;
|
again = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
SquadTriggerAbility squadETB = (SquadTriggerAbility)getSubAbilities().get(0);
|
ability.setCostsTag(SQUAD_ACTIVATION_VALUE_KEY,cost.getActivateCount());
|
||||||
squadETB.zcc = get_zcc(ability, game);
|
|
||||||
SquadEffectETB squadEffect = (SquadEffectETB)squadETB.getEffects().get(0);
|
|
||||||
squadEffect.activationCount = cost.getActivateCount();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -131,18 +104,8 @@ public class SquadAbility extends StaticAbility implements OptionalAdditionalSou
|
||||||
cost.getText()+"any number of times. When this creature enters the battlefield, "+
|
cost.getText()+"any number of times. When this creature enters the battlefield, "+
|
||||||
"create that many tokens that are copies of it.)</i>";
|
"create that many tokens that are copies of it.)</i>";
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Number of times squad cost was paid
|
|
||||||
*
|
|
||||||
* @return int activation count
|
|
||||||
*/
|
|
||||||
public int getSquadCount() {
|
|
||||||
return cost.getActivateCount();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
class SquadTriggerAbility extends EntersBattlefieldTriggeredAbility {
|
class SquadTriggerAbility extends EntersBattlefieldTriggeredAbility {
|
||||||
protected Integer zcc;
|
|
||||||
public SquadTriggerAbility() {
|
public SquadTriggerAbility() {
|
||||||
super(new SquadEffectETB());
|
super(new SquadEffectETB());
|
||||||
this.setRuleVisible(false);
|
this.setRuleVisible(false);
|
||||||
|
|
@ -150,7 +113,6 @@ class SquadTriggerAbility extends EntersBattlefieldTriggeredAbility {
|
||||||
|
|
||||||
private SquadTriggerAbility(final SquadTriggerAbility ability) {
|
private SquadTriggerAbility(final SquadTriggerAbility ability) {
|
||||||
super(ability);
|
super(ability);
|
||||||
this.zcc = ability.zcc;
|
|
||||||
}
|
}
|
||||||
@Override
|
@Override
|
||||||
public SquadTriggerAbility copy() {
|
public SquadTriggerAbility copy() {
|
||||||
|
|
@ -159,21 +121,17 @@ class SquadTriggerAbility extends EntersBattlefieldTriggeredAbility {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean checkInterveningIfClause(Game game) {
|
public boolean checkInterveningIfClause(Game game) {
|
||||||
if (zcc != null && zcc == SquadAbility.get_zcc(this, game)){
|
int squadCount = CardUtil.getSourceCostsTag(game, this, SquadAbility.SQUAD_ACTIVATION_VALUE_KEY,0);
|
||||||
SquadEffectETB effect = (SquadEffectETB)getEffects().get(0);
|
return (squadCount > 0);
|
||||||
return effect.activationCount > 0;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
@Override
|
@Override
|
||||||
public String getRule() {
|
public String getRule() {
|
||||||
return "Squad <i>(When this creature enters the battlefield, if its squad cost was paid, "
|
return "Squad <i>(When this creature enters the battlefield, if its squad cost was paid, "
|
||||||
+ "create a token that’s a copy of it for each time its squad cost was paid.)</i>";
|
+ "create a token that's a copy of it for each time its squad cost was paid.)</i>";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class SquadEffectETB extends OneShotEffect {
|
class SquadEffectETB extends OneShotEffect {
|
||||||
protected Integer activationCount;
|
|
||||||
|
|
||||||
SquadEffectETB() {
|
SquadEffectETB() {
|
||||||
super(Outcome.Benefit);
|
super(Outcome.Benefit);
|
||||||
|
|
@ -181,7 +139,6 @@ class SquadEffectETB extends OneShotEffect {
|
||||||
|
|
||||||
private SquadEffectETB(final SquadEffectETB effect) {
|
private SquadEffectETB(final SquadEffectETB effect) {
|
||||||
super(effect);
|
super(effect);
|
||||||
this.activationCount = effect.activationCount;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -191,10 +148,8 @@ class SquadEffectETB extends OneShotEffect {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean apply(Game game, Ability source) {
|
public boolean apply(Game game, Ability source) {
|
||||||
if (activationCount != null) {
|
int squadCount = CardUtil.getSourceCostsTag(game, source, SquadAbility.SQUAD_ACTIVATION_VALUE_KEY,0);
|
||||||
CreateTokenCopySourceEffect effect = new CreateTokenCopySourceEffect(activationCount);
|
CreateTokenCopySourceEffect effect = new CreateTokenCopySourceEffect(squadCount);
|
||||||
return effect.apply(game, source);
|
return effect.apply(game, source);
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,8 +10,6 @@ import mage.game.Game;
|
||||||
import mage.players.Player;
|
import mage.players.Player;
|
||||||
import mage.watchers.common.CastSpellLastTurnWatcher;
|
import mage.watchers.common.CastSpellLastTurnWatcher;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -65,15 +63,9 @@ public class SurgeAbility extends SpellAbility {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
public boolean activate(Game game, boolean noMana) {
|
public boolean activate(Game game, boolean noMana) {
|
||||||
if (super.activate(game, noMana)) {
|
if (super.activate(game, noMana)) {
|
||||||
List<Integer> surgeActivations = (ArrayList) game.getState().getValue(SURGE_ACTIVATION_VALUE_KEY + getSourceId());
|
this.setCostsTag(SURGE_ACTIVATION_VALUE_KEY, null);
|
||||||
if (surgeActivations == null) {
|
|
||||||
surgeActivations = new ArrayList<>(); // zoneChangeCounter
|
|
||||||
game.getState().setValue(SURGE_ACTIVATION_VALUE_KEY + getSourceId(), surgeActivations);
|
|
||||||
}
|
|
||||||
surgeActivations.add(game.getState().getZoneChangeCounter(getSourceId()));
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
|
|
||||||
|
|
@ -408,10 +408,6 @@ public class StackAbility extends StackObjectImpl implements Ability {
|
||||||
ability.setCostsTag(tag, value);
|
ability.setCostsTag(tag, value);
|
||||||
}
|
}
|
||||||
@Override
|
@Override
|
||||||
public Object getCostsTagOrDefault(String tag, Object defaultValue){
|
|
||||||
return ability.getCostsTagOrDefault(tag, defaultValue);
|
|
||||||
}
|
|
||||||
@Override
|
|
||||||
public AbilityType getAbilityType() {
|
public AbilityType getAbilityType() {
|
||||||
return ability.getAbilityType();
|
return ability.getAbilityType();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -282,27 +282,13 @@ public abstract class PlayerImpl implements Player, Serializable {
|
||||||
this.bufferTimeLeft = player.getBufferTimeLeft();
|
this.bufferTimeLeft = player.getBufferTimeLeft();
|
||||||
this.reachedNextTurnAfterLeaving = player.reachedNextTurnAfterLeaving;
|
this.reachedNextTurnAfterLeaving = player.reachedNextTurnAfterLeaving;
|
||||||
|
|
||||||
for (Entry<UUID, Set<MageIdentifier>> entry : player.getCastSourceIdWithAlternateMana().entrySet()) {
|
this.castSourceIdWithAlternateMana = CardUtil.deepCopyObject(player.castSourceIdWithAlternateMana);
|
||||||
this.castSourceIdWithAlternateMana.put(entry.getKey(), (entry.getValue() == null ? null : new HashSet<>(entry.getValue())));
|
this.castSourceIdManaCosts = CardUtil.deepCopyObject(player.castSourceIdManaCosts);
|
||||||
}
|
this.castSourceIdCosts = CardUtil.deepCopyObject(player.castSourceIdCosts);
|
||||||
for (Entry<UUID, Map<MageIdentifier, ManaCosts<ManaCost>>> entry : player.getCastSourceIdManaCosts().entrySet()) {
|
|
||||||
this.castSourceIdManaCosts.put(entry.getKey(), new HashMap<>());
|
|
||||||
for (Entry<MageIdentifier, ManaCosts<ManaCost>> subEntry : entry.getValue().entrySet()) {
|
|
||||||
this.castSourceIdManaCosts.get(entry.getKey()).put(subEntry.getKey(), subEntry.getValue() == null ? null : subEntry.getValue().copy());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (Entry<UUID, Map<MageIdentifier, Costs<Cost>>> entry : player.getCastSourceIdCosts().entrySet()) {
|
|
||||||
this.castSourceIdCosts.put(entry.getKey(), new HashMap<>());
|
|
||||||
for (Entry<MageIdentifier, Costs<Cost>> subEntry : entry.getValue().entrySet()) {
|
|
||||||
this.castSourceIdCosts.get(entry.getKey()).put(subEntry.getKey(), subEntry.getValue() == null ? null : subEntry.getValue().copy());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.payManaMode = player.payManaMode;
|
this.payManaMode = player.payManaMode;
|
||||||
this.phyrexianColors = player.getPhyrexianColors() != null ? player.phyrexianColors.copy() : null;
|
this.phyrexianColors = player.getPhyrexianColors() != null ? player.phyrexianColors.copy() : null;
|
||||||
for (Designation object : player.designations) {
|
this.designations = CardUtil.deepCopyObject(player.designations);
|
||||||
this.designations.add(object.copy());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
||||||
|
|
@ -1677,8 +1677,17 @@ public final class CardUtil {
|
||||||
return new MageObjectReference(ability.getSourceId(), zcc, game);
|
return new MageObjectReference(ability.getSourceId(), zcc, game);
|
||||||
}
|
}
|
||||||
|
|
||||||
//Use the two other functions below to access the tags, this is just the shared logic for them
|
/**
|
||||||
private static Map<String, Object> getCostsTags(Game game, Ability source) {
|
* Returns the entire cost tags map of either the source ability, or the permanent source of the ability. May be null.
|
||||||
|
* Works in any moment (even before source ability activated)
|
||||||
|
* Usually you should use one of the single tag functions instead: getSourceCostsTag() or checkSourceCostsTagExists()
|
||||||
|
* Use this function with caution, as it directly exposes the backing data structure.
|
||||||
|
*
|
||||||
|
* @param game
|
||||||
|
* @param source
|
||||||
|
* @return the tag map (or null)
|
||||||
|
*/
|
||||||
|
public static Map<String, Object> getSourceCostsTagsMap(Game game, Ability source) {
|
||||||
Map<String, Object> costTags;
|
Map<String, Object> costTags;
|
||||||
costTags = source.getCostsTagMap();
|
costTags = source.getCostsTagMap();
|
||||||
if (costTags == null && source.getSourcePermanentOrLKI(game) != null) {
|
if (costTags == null && source.getSourcePermanentOrLKI(game) != null) {
|
||||||
|
|
@ -1696,12 +1705,13 @@ public final class CardUtil {
|
||||||
* @return if the tag was found
|
* @return if the tag was found
|
||||||
*/
|
*/
|
||||||
public static boolean checkSourceCostsTagExists(Game game, Ability source, String tag) {
|
public static boolean checkSourceCostsTagExists(Game game, Ability source, String tag) {
|
||||||
Map<String, Object> costTags = getCostsTags(game, source);
|
Map<String, Object> costTags = getSourceCostsTagsMap(game, source);
|
||||||
return costTags != null && costTags.containsKey(tag);
|
return costTags != null && costTags.containsKey(tag);
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Find a specific tag in the cost tags of either the source ability, or the permanent source of the ability.
|
* Find a specific tag in the cost tags of either the source ability, or the permanent source of the ability.
|
||||||
* Works in any moment (even before source ability activated)
|
* Works in any moment (even before source ability activated)
|
||||||
|
* Do not use with null values, use checkSourceCostsTagExists instead
|
||||||
*
|
*
|
||||||
* @param game
|
* @param game
|
||||||
* @param source
|
* @param source
|
||||||
|
|
@ -1709,10 +1719,17 @@ public final class CardUtil {
|
||||||
* @param defaultValue A default value to return if the tag is not found
|
* @param defaultValue A default value to return if the tag is not found
|
||||||
* @return The object stored by the tag if found, the default if not
|
* @return The object stored by the tag if found, the default if not
|
||||||
*/
|
*/
|
||||||
public static Object getSourceCostsTag(Game game, Ability source, String tag, Object defaultValue){
|
public static <T> T getSourceCostsTag(Game game, Ability source, String tag, T defaultValue){
|
||||||
Map<String, Object> costTags = getCostsTags(game, source);
|
Map<String, Object> costTags = getSourceCostsTagsMap(game, source);
|
||||||
if (costTags != null) {
|
if (costTags != null) {
|
||||||
return costTags.getOrDefault(tag, defaultValue);
|
Object value = costTags.getOrDefault(tag, defaultValue);
|
||||||
|
if (value == null) {
|
||||||
|
throw new IllegalStateException("Wrong code usage: Costs tag " + tag + " has value stored of type null but is trying to be read. Use checkSourceCostsTagExists");
|
||||||
|
}
|
||||||
|
if (value.getClass() != defaultValue.getClass()) {
|
||||||
|
throw new IllegalStateException("Wrong code usage: Costs tag " + tag + " has value stored of type " + value.getClass().getName() + " different from default of type " + defaultValue.getClass().getName());
|
||||||
|
}
|
||||||
|
return (T) value;
|
||||||
}
|
}
|
||||||
return defaultValue;
|
return defaultValue;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue