From ae969b1797f30489db04359e603cd2e645007a18 Mon Sep 17 00:00:00 2001 From: Neil Gentleman Date: Mon, 19 Oct 2015 02:21:33 -0700 Subject: [PATCH 01/25] Fix incorrect casting costs why is 3UU so unpopular? --- Mage.Sets/src/mage/sets/magic2011/AjanisPridemate.java | 2 +- Mage.Sets/src/mage/sets/odyssey/BalshanGriffin.java | 2 +- Mage.Sets/src/mage/sets/odyssey/FerventDenial.java | 2 +- Mage.Sets/src/mage/sets/onslaught/RiptideShapeshifter.java | 2 +- Mage.Sets/src/mage/sets/urzassaga/Sunder.java | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Mage.Sets/src/mage/sets/magic2011/AjanisPridemate.java b/Mage.Sets/src/mage/sets/magic2011/AjanisPridemate.java index 57e2e4dd192..4272c6c4093 100644 --- a/Mage.Sets/src/mage/sets/magic2011/AjanisPridemate.java +++ b/Mage.Sets/src/mage/sets/magic2011/AjanisPridemate.java @@ -48,7 +48,7 @@ import mage.game.events.GameEvent.EventType; public class AjanisPridemate extends CardImpl { public AjanisPridemate(UUID ownerId) { - super(ownerId, 3, "Ajani's Pridemate", Rarity.UNCOMMON, new CardType[]{CardType.CREATURE}, "1}{W}"); + super(ownerId, 3, "Ajani's Pridemate", Rarity.UNCOMMON, new CardType[]{CardType.CREATURE}, "{1}{W}"); this.expansionSetCode = "M11"; this.subtype.add("Cat"); this.subtype.add("Soldier"); diff --git a/Mage.Sets/src/mage/sets/odyssey/BalshanGriffin.java b/Mage.Sets/src/mage/sets/odyssey/BalshanGriffin.java index 02662a1d3bc..fe4767b9919 100644 --- a/Mage.Sets/src/mage/sets/odyssey/BalshanGriffin.java +++ b/Mage.Sets/src/mage/sets/odyssey/BalshanGriffin.java @@ -49,7 +49,7 @@ import mage.target.common.TargetCardInHand; public class BalshanGriffin extends CardImpl { public BalshanGriffin(UUID ownerId) { - super(ownerId, 67, "Balshan Griffin", Rarity.UNCOMMON, new CardType[]{CardType.CREATURE}, "{3}{U}"); + super(ownerId, 67, "Balshan Griffin", Rarity.UNCOMMON, new CardType[]{CardType.CREATURE}, "{3}{U}{U}"); this.expansionSetCode = "ODY"; this.subtype.add("Griffin"); diff --git a/Mage.Sets/src/mage/sets/odyssey/FerventDenial.java b/Mage.Sets/src/mage/sets/odyssey/FerventDenial.java index 990232bc94e..24cb2a3e330 100644 --- a/Mage.Sets/src/mage/sets/odyssey/FerventDenial.java +++ b/Mage.Sets/src/mage/sets/odyssey/FerventDenial.java @@ -44,7 +44,7 @@ import mage.target.TargetSpell; public class FerventDenial extends CardImpl { public FerventDenial(UUID ownerId) { - super(ownerId, 86, "Fervent Denial", Rarity.UNCOMMON, new CardType[]{CardType.INSTANT}, "{3}{U}"); + super(ownerId, 86, "Fervent Denial", Rarity.UNCOMMON, new CardType[]{CardType.INSTANT}, "{3}{U}{U}"); this.expansionSetCode = "ODY"; diff --git a/Mage.Sets/src/mage/sets/onslaught/RiptideShapeshifter.java b/Mage.Sets/src/mage/sets/onslaught/RiptideShapeshifter.java index 72ff1eb3c97..615d8f2ee16 100644 --- a/Mage.Sets/src/mage/sets/onslaught/RiptideShapeshifter.java +++ b/Mage.Sets/src/mage/sets/onslaught/RiptideShapeshifter.java @@ -56,7 +56,7 @@ import mage.players.Player; public class RiptideShapeshifter extends CardImpl { public RiptideShapeshifter(UUID ownerId) { - super(ownerId, 109, "Riptide Shapeshifter", Rarity.UNCOMMON, new CardType[]{CardType.CREATURE}, "{3}{U}"); + super(ownerId, 109, "Riptide Shapeshifter", Rarity.UNCOMMON, new CardType[]{CardType.CREATURE}, "{3}{U}{U}"); this.expansionSetCode = "ONS"; this.subtype.add("Shapeshifter"); diff --git a/Mage.Sets/src/mage/sets/urzassaga/Sunder.java b/Mage.Sets/src/mage/sets/urzassaga/Sunder.java index ee8ff761003..fee91fcee5e 100644 --- a/Mage.Sets/src/mage/sets/urzassaga/Sunder.java +++ b/Mage.Sets/src/mage/sets/urzassaga/Sunder.java @@ -46,7 +46,7 @@ import mage.game.permanent.Permanent; public class Sunder extends CardImpl { public Sunder(UUID ownerId) { - super(ownerId, 101, "Sunder", Rarity.RARE, new CardType[]{CardType.INSTANT}, "{3}{U}"); + super(ownerId, 101, "Sunder", Rarity.RARE, new CardType[]{CardType.INSTANT}, "{3}{U}{U}"); this.expansionSetCode = "USG"; // Return all lands to their owners' hands. From e3d83c3545f66ed79df22b7e2d037e01f9f17b2c Mon Sep 17 00:00:00 2001 From: Neil Gentleman Date: Mon, 19 Oct 2015 02:32:58 -0700 Subject: [PATCH 02/25] Fix Minamo Sightbender toughness --- .../src/mage/sets/betrayersofkamigawa/MinamoSightbender.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Mage.Sets/src/mage/sets/betrayersofkamigawa/MinamoSightbender.java b/Mage.Sets/src/mage/sets/betrayersofkamigawa/MinamoSightbender.java index 10062690021..e10d7262166 100644 --- a/Mage.Sets/src/mage/sets/betrayersofkamigawa/MinamoSightbender.java +++ b/Mage.Sets/src/mage/sets/betrayersofkamigawa/MinamoSightbender.java @@ -62,7 +62,7 @@ public class MinamoSightbender extends CardImpl { this.subtype.add("Wizard"); this.power = new MageInt(1); - this.toughness = new MageInt(1); + this.toughness = new MageInt(2); // {X}, {T}: Target creature with power X or less can't be blocked this turn. Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new CantBeBlockedTargetEffect(), new ManaCostsImpl("{X}")); From 77c1b827bd10e1426e17186c82045a267aa39452 Mon Sep 17 00:00:00 2001 From: Neil Gentleman Date: Mon, 19 Oct 2015 02:58:15 -0700 Subject: [PATCH 03/25] Fix misc. creature types and missing Legendary & Arcane --- Mage.Sets/src/mage/sets/archenemy/SkirkCommando.java | 1 - Mage.Sets/src/mage/sets/battleforzendikar/DustStalker.java | 3 +-- .../src/mage/sets/betrayersofkamigawa/GenjuOfTheRealm.java | 1 + .../src/mage/sets/championsofkamigawa/CrushingPain.java | 2 +- .../src/mage/sets/championsofkamigawa/EtherealHaze.java | 2 +- Mage.Sets/src/mage/sets/conflux/EtherswornAdjudicator.java | 2 ++ .../src/mage/sets/dragonsmaze/SmeltWardGatekeepers.java | 2 +- Mage.Sets/src/mage/sets/fifthedition/ShanodinDryads.java | 1 + Mage.Sets/src/mage/sets/gatecrash/UrbanEvolution.java | 4 ---- Mage.Sets/src/mage/sets/journeyintonyx/PensiveMinotaur.java | 1 + Mage.Sets/src/mage/sets/magic2010/Lifelink.java | 4 ---- Mage.Sets/src/mage/sets/planechase/SilverMyr.java | 1 - .../src/mage/sets/returntoravnica/SphinxOfTheChimes.java | 2 +- .../mage/sets/saviorsofkamigawa/MatsuTribeBirdstalker.java | 1 + Mage.Sets/src/mage/sets/scarsofmirrodin/OxiddaDaredevil.java | 5 ++++- Mage.Sets/src/mage/sets/scourge/GoblinWarchief.java | 1 + Mage.Sets/src/mage/sets/tenthedition/HighwayRobber.java | 1 - Mage.Sets/src/mage/sets/urzassaga/GorillaWarrior.java | 1 + Mage.Sets/src/mage/sets/zendikar/ArrowVolleyTrap.java | 1 + 19 files changed, 18 insertions(+), 18 deletions(-) diff --git a/Mage.Sets/src/mage/sets/archenemy/SkirkCommando.java b/Mage.Sets/src/mage/sets/archenemy/SkirkCommando.java index 3dd3c0fc6d5..c364916e666 100644 --- a/Mage.Sets/src/mage/sets/archenemy/SkirkCommando.java +++ b/Mage.Sets/src/mage/sets/archenemy/SkirkCommando.java @@ -52,7 +52,6 @@ public class SkirkCommando extends CardImpl { super(ownerId, 47, "Skirk Commando", Rarity.COMMON, new CardType[]{CardType.CREATURE}, "{1}{R}{R}"); this.expansionSetCode = "ARC"; this.subtype.add("Goblin"); - this.subtype.add("Shaman"); this.power = new MageInt(2); this.toughness = new MageInt(1); diff --git a/Mage.Sets/src/mage/sets/battleforzendikar/DustStalker.java b/Mage.Sets/src/mage/sets/battleforzendikar/DustStalker.java index cd751056f6b..15b94094f9c 100644 --- a/Mage.Sets/src/mage/sets/battleforzendikar/DustStalker.java +++ b/Mage.Sets/src/mage/sets/battleforzendikar/DustStalker.java @@ -59,8 +59,7 @@ public class DustStalker extends CardImpl { public DustStalker(UUID ownerId) { super(ownerId, 202, "Dust Stalker", Rarity.RARE, new CardType[]{CardType.CREATURE}, "{2}{B}{R}"); this.expansionSetCode = "BFZ"; - this.supertype.add("Creautre"); - this.supertype.add("Eldrazi"); + this.subtype.add("Eldrazi"); this.power = new MageInt(5); this.toughness = new MageInt(3); diff --git a/Mage.Sets/src/mage/sets/betrayersofkamigawa/GenjuOfTheRealm.java b/Mage.Sets/src/mage/sets/betrayersofkamigawa/GenjuOfTheRealm.java index d973dc72a98..6afd9f2b81a 100644 --- a/Mage.Sets/src/mage/sets/betrayersofkamigawa/GenjuOfTheRealm.java +++ b/Mage.Sets/src/mage/sets/betrayersofkamigawa/GenjuOfTheRealm.java @@ -62,6 +62,7 @@ public class GenjuOfTheRealm extends CardImpl { super(ownerId, 151, "Genju of the Realm", Rarity.RARE, new CardType[]{CardType.ENCHANTMENT}, "{W}{U}{B}{R}{G}"); this.expansionSetCode = "BOK"; this.subtype.add("Aura"); + this.supertype.add("Legendary"); // Enchant Land diff --git a/Mage.Sets/src/mage/sets/championsofkamigawa/CrushingPain.java b/Mage.Sets/src/mage/sets/championsofkamigawa/CrushingPain.java index 048fa176252..8d797e53819 100644 --- a/Mage.Sets/src/mage/sets/championsofkamigawa/CrushingPain.java +++ b/Mage.Sets/src/mage/sets/championsofkamigawa/CrushingPain.java @@ -54,7 +54,7 @@ public class CrushingPain extends CardImpl { public CrushingPain (UUID ownerId) { super(ownerId, 162, "Crushing Pain", Rarity.COMMON, new CardType[]{CardType.INSTANT}, "{1}{R}"); this.expansionSetCode = "CHK"; - + this.subtype.add("Arcane"); // Crushing Pain deals 6 damage to target creature that was dealt damage this turn. this.getSpellAbility().addEffect(new DamageTargetEffect(6)); diff --git a/Mage.Sets/src/mage/sets/championsofkamigawa/EtherealHaze.java b/Mage.Sets/src/mage/sets/championsofkamigawa/EtherealHaze.java index 8c6eb77fe52..308de9cef81 100644 --- a/Mage.Sets/src/mage/sets/championsofkamigawa/EtherealHaze.java +++ b/Mage.Sets/src/mage/sets/championsofkamigawa/EtherealHaze.java @@ -45,7 +45,7 @@ public class EtherealHaze extends CardImpl { public EtherealHaze (UUID ownerId) { super(ownerId, 9, "Ethereal Haze", Rarity.COMMON, new CardType[]{CardType.INSTANT}, "{W}"); this.expansionSetCode = "CHK"; - + this.subtype.add("Arcane"); // Prevent all damage that would be dealt by creatures this turn. this.getSpellAbility().addEffect(new PreventAllDamageByAllEffect(new FilterCreaturePermanent("creatures"), Duration.EndOfTurn, false)); diff --git a/Mage.Sets/src/mage/sets/conflux/EtherswornAdjudicator.java b/Mage.Sets/src/mage/sets/conflux/EtherswornAdjudicator.java index 96d9df3d93d..6b5c29dcde7 100644 --- a/Mage.Sets/src/mage/sets/conflux/EtherswornAdjudicator.java +++ b/Mage.Sets/src/mage/sets/conflux/EtherswornAdjudicator.java @@ -63,6 +63,8 @@ public class EtherswornAdjudicator extends CardImpl { public EtherswornAdjudicator(UUID ownerId) { super(ownerId, 26, "Ethersworn Adjudicator", Rarity.MYTHIC, new CardType[]{CardType.ARTIFACT, CardType.CREATURE}, "{4}{U}"); this.expansionSetCode = "CON"; + this.subtype.add("Vedalken"); + this.subtype.add("Knight"); this.power = new MageInt(4); this.toughness = new MageInt(4); diff --git a/Mage.Sets/src/mage/sets/dragonsmaze/SmeltWardGatekeepers.java b/Mage.Sets/src/mage/sets/dragonsmaze/SmeltWardGatekeepers.java index 95f3582d94d..e79e5ae9797 100644 --- a/Mage.Sets/src/mage/sets/dragonsmaze/SmeltWardGatekeepers.java +++ b/Mage.Sets/src/mage/sets/dragonsmaze/SmeltWardGatekeepers.java @@ -70,7 +70,7 @@ public class SmeltWardGatekeepers extends CardImpl { super(ownerId, 39, "Smelt-Ward Gatekeepers", Rarity.COMMON, new CardType[]{CardType.CREATURE}, "{3}{R}"); this.expansionSetCode = "DGM"; this.subtype.add("Human"); - this.subtype.add("Soldier"); + this.subtype.add("Warrior"); this.power = new MageInt(2); this.toughness = new MageInt(4); diff --git a/Mage.Sets/src/mage/sets/fifthedition/ShanodinDryads.java b/Mage.Sets/src/mage/sets/fifthedition/ShanodinDryads.java index 9d7ff24671b..7d077635084 100644 --- a/Mage.Sets/src/mage/sets/fifthedition/ShanodinDryads.java +++ b/Mage.Sets/src/mage/sets/fifthedition/ShanodinDryads.java @@ -43,6 +43,7 @@ public class ShanodinDryads extends CardImpl { public ShanodinDryads(UUID ownerId) { super(ownerId, 187, "Shanodin Dryads", Rarity.COMMON, new CardType[]{CardType.CREATURE}, "{G}"); this.expansionSetCode = "5ED"; + this.subtype.add("Nymph"); this.subtype.add("Dryad"); this.power = new MageInt(1); diff --git a/Mage.Sets/src/mage/sets/gatecrash/UrbanEvolution.java b/Mage.Sets/src/mage/sets/gatecrash/UrbanEvolution.java index 626a9642465..53128276399 100644 --- a/Mage.Sets/src/mage/sets/gatecrash/UrbanEvolution.java +++ b/Mage.Sets/src/mage/sets/gatecrash/UrbanEvolution.java @@ -46,16 +46,12 @@ public class UrbanEvolution extends CardImpl { public UrbanEvolution(UUID ownerId) { super(ownerId, 204, "Urban Evolution", Rarity.UNCOMMON, new CardType[]{CardType.SORCERY}, "{3}{U}{G}"); this.expansionSetCode = "GTC"; - this.subtype.add("Wizard"); - //Draw three cards. this.getSpellAbility().addEffect(new DrawCardSourceControllerEffect(3)); //You may play an additional land this turn. this.getSpellAbility().addEffect(new PlayAdditionalLandsControllerEffect(1, Duration.EndOfTurn)); - - } public UrbanEvolution(final UrbanEvolution card) { diff --git a/Mage.Sets/src/mage/sets/journeyintonyx/PensiveMinotaur.java b/Mage.Sets/src/mage/sets/journeyintonyx/PensiveMinotaur.java index 81c49ed6e25..6fcac8eda9a 100644 --- a/Mage.Sets/src/mage/sets/journeyintonyx/PensiveMinotaur.java +++ b/Mage.Sets/src/mage/sets/journeyintonyx/PensiveMinotaur.java @@ -43,6 +43,7 @@ public class PensiveMinotaur extends CardImpl { super(ownerId, 105, "Pensive Minotaur", Rarity.COMMON, new CardType[]{CardType.CREATURE}, "{2}{R}"); this.expansionSetCode = "JOU"; this.subtype.add("Minotaur"); + this.subtype.add("Warrior"); this.power = new MageInt(2); this.toughness = new MageInt(3); diff --git a/Mage.Sets/src/mage/sets/magic2010/Lifelink.java b/Mage.Sets/src/mage/sets/magic2010/Lifelink.java index b74e6c36ab6..b46a46d7013 100644 --- a/Mage.Sets/src/mage/sets/magic2010/Lifelink.java +++ b/Mage.Sets/src/mage/sets/magic2010/Lifelink.java @@ -40,10 +40,6 @@ public class Lifelink extends mage.sets.magic2012.Lifelink { super(ownerId); this.cardNumber = 18; this.expansionSetCode = "M10"; - this.subtype.add("Aura"); - - this.color.setWhite(true); - } public Lifelink (final Lifelink card) { diff --git a/Mage.Sets/src/mage/sets/planechase/SilverMyr.java b/Mage.Sets/src/mage/sets/planechase/SilverMyr.java index 97f6440f77c..84d30675695 100644 --- a/Mage.Sets/src/mage/sets/planechase/SilverMyr.java +++ b/Mage.Sets/src/mage/sets/planechase/SilverMyr.java @@ -40,7 +40,6 @@ public class SilverMyr extends mage.sets.mirrodin.SilverMyr { super(ownerId); this.cardNumber = 126; this.expansionSetCode = "HOP"; - this.subtype.add("Myr"); } public SilverMyr (final SilverMyr card) { diff --git a/Mage.Sets/src/mage/sets/returntoravnica/SphinxOfTheChimes.java b/Mage.Sets/src/mage/sets/returntoravnica/SphinxOfTheChimes.java index aba60a810f2..21055514d8d 100644 --- a/Mage.Sets/src/mage/sets/returntoravnica/SphinxOfTheChimes.java +++ b/Mage.Sets/src/mage/sets/returntoravnica/SphinxOfTheChimes.java @@ -61,7 +61,7 @@ public class SphinxOfTheChimes extends CardImpl { public SphinxOfTheChimes(UUID ownerId) { super(ownerId, 52, "Sphinx of the Chimes", Rarity.RARE, new CardType[]{CardType.CREATURE}, "{4}{U}{U}"); this.expansionSetCode = "RTR"; - this.subtype.add("Bird"); + this.subtype.add("Sphinx"); this.power = new MageInt(5); this.toughness = new MageInt(6); diff --git a/Mage.Sets/src/mage/sets/saviorsofkamigawa/MatsuTribeBirdstalker.java b/Mage.Sets/src/mage/sets/saviorsofkamigawa/MatsuTribeBirdstalker.java index 34fed8bc030..0ba6ed34b44 100644 --- a/Mage.Sets/src/mage/sets/saviorsofkamigawa/MatsuTribeBirdstalker.java +++ b/Mage.Sets/src/mage/sets/saviorsofkamigawa/MatsuTribeBirdstalker.java @@ -55,6 +55,7 @@ public class MatsuTribeBirdstalker extends CardImpl { this.expansionSetCode = "SOK"; this.subtype.add("Snake"); this.subtype.add("Warrior"); + this.subtype.add("Archer"); this.power = new MageInt(2); this.toughness = new MageInt(2); diff --git a/Mage.Sets/src/mage/sets/scarsofmirrodin/OxiddaDaredevil.java b/Mage.Sets/src/mage/sets/scarsofmirrodin/OxiddaDaredevil.java index 834ecd9a51d..65a8259d5bd 100644 --- a/Mage.Sets/src/mage/sets/scarsofmirrodin/OxiddaDaredevil.java +++ b/Mage.Sets/src/mage/sets/scarsofmirrodin/OxiddaDaredevil.java @@ -58,9 +58,12 @@ public class OxiddaDaredevil extends CardImpl { public OxiddaDaredevil (UUID ownerId) { super(ownerId, 100, "Oxidda Daredevil", Rarity.COMMON, new CardType[]{CardType.CREATURE}, "{1}{R}"); this.expansionSetCode = "SOM"; - this.color.setRed(true); + this.subtype.add("Goblin"); + this.subtype.add("Artificer"); + this.power = new MageInt(2); this.toughness = new MageInt(1); + this.addAbility(new SimpleActivatedAbility(Zone.BATTLEFIELD, new GainAbilitySourceEffect(HasteAbility.getInstance(), Duration.EndOfTurn), new SacrificeTargetCost(new TargetControlledPermanent(filter)))); diff --git a/Mage.Sets/src/mage/sets/scourge/GoblinWarchief.java b/Mage.Sets/src/mage/sets/scourge/GoblinWarchief.java index 0d62d55ccd7..298f2be5c4a 100644 --- a/Mage.Sets/src/mage/sets/scourge/GoblinWarchief.java +++ b/Mage.Sets/src/mage/sets/scourge/GoblinWarchief.java @@ -60,6 +60,7 @@ public class GoblinWarchief extends CardImpl { super(ownerId, 97, "Goblin Warchief", Rarity.UNCOMMON, new CardType[]{CardType.CREATURE}, "{1}{R}{R}"); this.expansionSetCode = "SCG"; this.subtype.add("Goblin"); + this.subtype.add("Warrior"); this.power = new MageInt(2); this.toughness = new MageInt(2); diff --git a/Mage.Sets/src/mage/sets/tenthedition/HighwayRobber.java b/Mage.Sets/src/mage/sets/tenthedition/HighwayRobber.java index 6937d8e1ef0..6b55da06104 100644 --- a/Mage.Sets/src/mage/sets/tenthedition/HighwayRobber.java +++ b/Mage.Sets/src/mage/sets/tenthedition/HighwayRobber.java @@ -48,7 +48,6 @@ public class HighwayRobber extends CardImpl { super(ownerId, 150, "Highway Robber", Rarity.COMMON, new CardType[]{CardType.CREATURE}, "{2}{B}{B}"); this.expansionSetCode = "10E"; this.subtype.add("Human"); - this.subtype.add("Rogue"); this.subtype.add("Mercenary"); this.power = new MageInt(2); diff --git a/Mage.Sets/src/mage/sets/urzassaga/GorillaWarrior.java b/Mage.Sets/src/mage/sets/urzassaga/GorillaWarrior.java index 5c20c56b30c..3149b5f07a4 100644 --- a/Mage.Sets/src/mage/sets/urzassaga/GorillaWarrior.java +++ b/Mage.Sets/src/mage/sets/urzassaga/GorillaWarrior.java @@ -44,6 +44,7 @@ public class GorillaWarrior extends CardImpl { super(ownerId, 256, "Gorilla Warrior", Rarity.COMMON, new CardType[]{CardType.CREATURE}, "{2}{G}"); this.expansionSetCode = "USG"; this.subtype.add("Ape"); + this.subtype.add("Warrior"); this.power = new MageInt(3); this.toughness = new MageInt(2); diff --git a/Mage.Sets/src/mage/sets/zendikar/ArrowVolleyTrap.java b/Mage.Sets/src/mage/sets/zendikar/ArrowVolleyTrap.java index cf786931cf8..a83b34aa210 100644 --- a/Mage.Sets/src/mage/sets/zendikar/ArrowVolleyTrap.java +++ b/Mage.Sets/src/mage/sets/zendikar/ArrowVolleyTrap.java @@ -47,6 +47,7 @@ public class ArrowVolleyTrap extends CardImpl { public ArrowVolleyTrap(UUID ownerId) { super(ownerId, 2, "Arrow Volley Trap", Rarity.UNCOMMON, new CardType[]{CardType.INSTANT}, "{3}{W}{W}"); this.expansionSetCode = "ZEN"; + this.subtype.add("Trap"); // If four or more creatures are attacking, you may pay {1}{W} rather than pay Arrow Volley Trap's mana cost. this.getSpellAbility().addAlternativeCost(new ArrowVolleyTrapAlternativeCost()); From 7e9205e90966a502d55492e122c466d963434c78 Mon Sep 17 00:00:00 2001 From: Neil Gentleman Date: Mon, 19 Oct 2015 03:17:17 -0700 Subject: [PATCH 04/25] Fix incorrect CardTypes --- .../src/mage/sets/championsofkamigawa/UnearthlyBlizzard.java | 2 +- Mage.Sets/src/mage/sets/lorwyn/FodderLaunch.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Mage.Sets/src/mage/sets/championsofkamigawa/UnearthlyBlizzard.java b/Mage.Sets/src/mage/sets/championsofkamigawa/UnearthlyBlizzard.java index 242df18ed16..29f76f4196d 100644 --- a/Mage.Sets/src/mage/sets/championsofkamigawa/UnearthlyBlizzard.java +++ b/Mage.Sets/src/mage/sets/championsofkamigawa/UnearthlyBlizzard.java @@ -43,7 +43,7 @@ import mage.target.common.TargetCreaturePermanent; public class UnearthlyBlizzard extends CardImpl { public UnearthlyBlizzard(UUID ownerId) { - super(ownerId, 196, "Unearthly Blizzard", Rarity.COMMON, new CardType[]{CardType.INSTANT}, "{2}{R}"); + super(ownerId, 196, "Unearthly Blizzard", Rarity.COMMON, new CardType[]{CardType.SORCERY}, "{2}{R}"); this.expansionSetCode = "CHK"; this.subtype.add("Arcane"); diff --git a/Mage.Sets/src/mage/sets/lorwyn/FodderLaunch.java b/Mage.Sets/src/mage/sets/lorwyn/FodderLaunch.java index 5fbf233ad1c..8ebbf2b0b79 100644 --- a/Mage.Sets/src/mage/sets/lorwyn/FodderLaunch.java +++ b/Mage.Sets/src/mage/sets/lorwyn/FodderLaunch.java @@ -46,7 +46,7 @@ import mage.target.common.TargetCreaturePermanent; public class FodderLaunch extends CardImpl { public FodderLaunch(UUID ownerId) { - super(ownerId, 114, "Fodder Launch", Rarity.UNCOMMON, new CardType[]{CardType.SORCERY}, "{3}{B}"); + super(ownerId, 114, "Fodder Launch", Rarity.UNCOMMON, new CardType[]{CardType.TRIBAL, CardType.SORCERY}, "{3}{B}"); this.expansionSetCode = "LRW"; this.subtype.add("Goblin"); From 8d71ddc938ac51edf3bdf60a6af88b25dafc5c32 Mon Sep 17 00:00:00 2001 From: Neil Gentleman Date: Mon, 19 Oct 2015 03:29:44 -0700 Subject: [PATCH 05/25] Fix color for Archdemon of Greed --- Mage.Sets/src/mage/sets/darkascension/ArchdemonOfGreed.java | 1 + 1 file changed, 1 insertion(+) diff --git a/Mage.Sets/src/mage/sets/darkascension/ArchdemonOfGreed.java b/Mage.Sets/src/mage/sets/darkascension/ArchdemonOfGreed.java index 42fc893a116..3fc7222beac 100644 --- a/Mage.Sets/src/mage/sets/darkascension/ArchdemonOfGreed.java +++ b/Mage.Sets/src/mage/sets/darkascension/ArchdemonOfGreed.java @@ -65,6 +65,7 @@ public class ArchdemonOfGreed extends CardImpl { super(ownerId, 71, "Archdemon of Greed", Rarity.RARE, new CardType[]{CardType.CREATURE}, ""); this.expansionSetCode = "DKA"; this.subtype.add("Demon"); + this.color.setBlack(true); this.nightCard = true; this.canTransform = true; From f72ec06ecdeda383ba8fddabb823aa1cb7204109 Mon Sep 17 00:00:00 2001 From: LevelX2 Date: Mon, 19 Oct 2015 17:45:26 +0200 Subject: [PATCH 06/25] * Serpentine Spike - Fixed that the three targets had not to be different. --- .../battleforzendikar/SerpentineSpike.java | 21 ++++- Mage.Sets/src/mage/sets/exodus/KorChant.java | 42 +++------- .../sets/jacevsvraska/DroolingGroodion.java | 39 +++------ .../mage/sets/tempestremastered/Deadshot.java | 47 ++++------- .../mageobject/AnotherTargetPredicate.java | 72 +++++++++++++++++ Mage/src/mage/target/Target.java | 4 + Mage/src/mage/target/TargetImpl.java | 19 +++++ Mage/src/mage/target/TargetPermanent.java | 80 +++++++++---------- 8 files changed, 195 insertions(+), 129 deletions(-) create mode 100644 Mage/src/mage/filter/predicate/mageobject/AnotherTargetPredicate.java diff --git a/Mage.Sets/src/mage/sets/battleforzendikar/SerpentineSpike.java b/Mage.Sets/src/mage/sets/battleforzendikar/SerpentineSpike.java index b6de1e9f1a6..292dc45afd9 100644 --- a/Mage.Sets/src/mage/sets/battleforzendikar/SerpentineSpike.java +++ b/Mage.Sets/src/mage/sets/battleforzendikar/SerpentineSpike.java @@ -39,6 +39,7 @@ import mage.constants.Duration; import mage.constants.Outcome; import mage.constants.Rarity; import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.mageobject.AnotherTargetPredicate; import mage.game.Game; import mage.game.permanent.Permanent; import mage.target.common.TargetCreaturePermanent; @@ -60,9 +61,23 @@ public class SerpentineSpike extends CardImpl { this.addAbility(ability); // Serpentine Spike deals 2 damage to target creature, 3 damage to another target creature, and 4 damage to a third target creature. If a creature dealt damage this way would die this turn, exile it instead. this.getSpellAbility().addEffect(new SerpentineSpikeEffect()); - this.getSpellAbility().addTarget(new TargetCreaturePermanent(new FilterCreaturePermanent("creature (2 damage)"))); - this.getSpellAbility().addTarget(new TargetCreaturePermanent(new FilterCreaturePermanent("creature (3 damage)"))); - this.getSpellAbility().addTarget(new TargetCreaturePermanent(new FilterCreaturePermanent("creature (4 damage)"))); + + TargetCreaturePermanent target = new TargetCreaturePermanent(new FilterCreaturePermanent("creature (2 damage)")); + target.setTargetTag(1); + this.getSpellAbility().addTarget(target); + + FilterCreaturePermanent filter = new FilterCreaturePermanent("another target creature (3 damage)"); + filter.add(new AnotherTargetPredicate(2)); + target = new TargetCreaturePermanent(filter); + target.setTargetTag(2); + this.getSpellAbility().addTarget(target); + + filter = new FilterCreaturePermanent("another target creature (4 damage)"); + filter.add(new AnotherTargetPredicate(3)); + target = new TargetCreaturePermanent(filter); + target.setTargetTag(3); + this.getSpellAbility().addTarget(target); + Effect effect = new DealtDamageToCreatureBySourceDies(this, Duration.EndOfTurn); effect.setText("If a creature dealt damage this way would die this turn, exile it instead"); this.getSpellAbility().addEffect(effect); diff --git a/Mage.Sets/src/mage/sets/exodus/KorChant.java b/Mage.Sets/src/mage/sets/exodus/KorChant.java index 0269d443edd..3aa8d9effb6 100644 --- a/Mage.Sets/src/mage/sets/exodus/KorChant.java +++ b/Mage.Sets/src/mage/sets/exodus/KorChant.java @@ -35,6 +35,8 @@ import mage.constants.CardType; import mage.constants.Duration; import mage.constants.Outcome; import mage.constants.Rarity; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.mageobject.AnotherTargetPredicate; import mage.game.Game; import mage.game.events.GameEvent; import mage.target.TargetSource; @@ -53,8 +55,15 @@ public class KorChant extends CardImpl { // All damage that would be dealt this turn to target creature you control by a source of your choice is dealt to another target creature instead. this.getSpellAbility().addEffect(new KorChantEffect()); - this.getSpellAbility().addTarget(new TargetControlledCreaturePermanent()); - this.getSpellAbility().addTarget(new KorChantSecondTarget()); + TargetControlledCreaturePermanent target = new TargetControlledCreaturePermanent(); + target.setTargetTag(1); + this.getSpellAbility().addTarget(target); + + FilterCreaturePermanent filter = new FilterCreaturePermanent("another target creature"); + filter.add(new AnotherTargetPredicate(2)); + TargetCreaturePermanent target2 = new TargetCreaturePermanent(filter); + target2.setTargetTag(2); + this.getSpellAbility().addTarget(target2); } public KorChant(final KorChant card) { @@ -67,33 +76,8 @@ public class KorChant extends CardImpl { } } -class KorChantSecondTarget extends TargetCreaturePermanent { - - KorChantSecondTarget() { - super(); - this.targetName = "another creature"; - } - - KorChantSecondTarget(final KorChantSecondTarget target) { - super(target); - } - - @Override - public boolean canTarget(UUID controllerId, UUID id, Ability source, Game game) { - if (source.getTargets().get(0).getTargets().contains(id)) { - return false; - } - return super.canTarget(controllerId, id, source, game); - } - - @Override - public KorChantSecondTarget copy() { - return new KorChantSecondTarget(this); - } -} - class KorChantEffect extends RedirectionEffect { - + protected TargetSource target = new TargetSource(); KorChantEffect() { @@ -121,7 +105,7 @@ class KorChantEffect extends RedirectionEffect { public boolean checksEventType(GameEvent event, Game game) { return event.getType() == GameEvent.EventType.DAMAGE_CREATURE; } - + @Override public boolean applies(GameEvent event, Ability source, Game game) { if (event.getTargetId().equals(this.getTargetPointer().getFirst(game, source)) diff --git a/Mage.Sets/src/mage/sets/jacevsvraska/DroolingGroodion.java b/Mage.Sets/src/mage/sets/jacevsvraska/DroolingGroodion.java index b27d9824f79..dda545b51ea 100644 --- a/Mage.Sets/src/mage/sets/jacevsvraska/DroolingGroodion.java +++ b/Mage.Sets/src/mage/sets/jacevsvraska/DroolingGroodion.java @@ -44,6 +44,7 @@ import mage.constants.SubLayer; import mage.constants.Zone; import mage.filter.common.FilterControlledCreaturePermanent; import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.mageobject.AnotherTargetPredicate; import mage.game.Game; import mage.game.permanent.Permanent; import mage.target.common.TargetControlledCreaturePermanent; @@ -66,8 +67,17 @@ public class DroolingGroodion extends CardImpl { // {2}{B}{G}, Sacrifice a creature: Target creature gets +2/+2 until end of turn. Another target creature gets -2/-2 until end of turn. Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new DroolingGroodionEffect(), new ManaCostsImpl("{2}{B}{G}")); ability.addCost(new SacrificeTargetCost(new TargetControlledCreaturePermanent(1, 1, new FilterControlledCreaturePermanent(), true))); - ability.addTarget(new TargetCreaturePermanent(new FilterCreaturePermanent("creature (first target)"))); - ability.addTarget(new TargetOtherCreaturePermanent(new FilterCreaturePermanent("creature (second target)"))); + + TargetCreaturePermanent target = new TargetCreaturePermanent(new FilterCreaturePermanent("creature (first target)")); + target.setTargetTag(1); + ability.addTarget(target); + + FilterCreaturePermanent filter = new FilterCreaturePermanent("another target creature (second target"); + filter.add(new AnotherTargetPredicate(2)); + target = new TargetCreaturePermanent(filter); + target.setTargetTag(2); + ability.addTarget(target); + this.addAbility(ability); } @@ -112,28 +122,3 @@ class DroolingGroodionEffect extends ContinuousEffectImpl { return true; } } - -class TargetOtherCreaturePermanent extends TargetCreaturePermanent { - - public TargetOtherCreaturePermanent(FilterCreaturePermanent filter) { - super(filter); - } - - public TargetOtherCreaturePermanent(final TargetOtherCreaturePermanent target) { - super(target); - } - - @Override - public boolean canTarget(UUID controllerId, UUID id, Ability source, Game game) { - if (source.getTargets().get(0).getTargets().contains(id)) { - return false; - } - return super.canTarget(controllerId, id, source, game); - } - - @Override - public TargetOtherCreaturePermanent copy() { - return new TargetOtherCreaturePermanent(this); - } - -} diff --git a/Mage.Sets/src/mage/sets/tempestremastered/Deadshot.java b/Mage.Sets/src/mage/sets/tempestremastered/Deadshot.java index e2fd17d8826..1aa0222736a 100644 --- a/Mage.Sets/src/mage/sets/tempestremastered/Deadshot.java +++ b/Mage.Sets/src/mage/sets/tempestremastered/Deadshot.java @@ -36,29 +36,39 @@ import mage.constants.CardType; import mage.constants.Outcome; import mage.constants.Rarity; import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.mageobject.AnotherTargetPredicate; import mage.game.Game; import mage.game.permanent.Permanent; import mage.target.common.TargetCreaturePermanent; +import mage.target.targetpointer.SecondTargetPointer; /** * * @author fireshoes */ public class Deadshot extends CardImpl { - + private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("another target creature"); + static { + filter.add(new AnotherTargetPredicate(2)); + } + public Deadshot(UUID ownerId) { super(ownerId, 129, "Deadshot", Rarity.UNCOMMON, new CardType[]{CardType.SORCERY}, "{3}{R}"); this.expansionSetCode = "TPR"; // Tap target creature. this.getSpellAbility().addEffect(new TapTargetEffect()); - this.getSpellAbility().addTarget(new TargetCreaturePermanent()); - + TargetCreaturePermanent target = new TargetCreaturePermanent(); + target.setTargetTag(1); + this.getSpellAbility().addTarget(target); + // It deals damage equal to its power to another target creature. this.getSpellAbility().addEffect(new DeadshotDamageEffect()); - this.getSpellAbility().addTarget(new DeadshotTargetCreaturePermanent(filter)); + target = new TargetCreaturePermanent(filter); + target.setTargetTag(2); + this.getSpellAbility().addTarget(target); } public Deadshot(final Deadshot card) { @@ -80,6 +90,7 @@ class DeadshotDamageEffect extends OneShotEffect { public DeadshotDamageEffect(final DeadshotDamageEffect effect) { super(effect); + this.setTargetPointer(new SecondTargetPointer()); } @Override @@ -89,10 +100,10 @@ class DeadshotDamageEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Permanent ownCreature = game.getPermanent(source.getFirstTarget()); + Permanent ownCreature = game.getPermanentOrLKIBattlefield(source.getFirstTarget()); if (ownCreature != null) { int damage = ownCreature.getPower().getValue(); - Permanent targetCreature = game.getPermanent(source.getTargets().get(1).getFirstTarget()); + Permanent targetCreature = game.getPermanent(getTargetPointer().getFirst(game, source)); if (targetCreature != null) { targetCreature.damage(damage, ownCreature.getId(), game, false, true); return true; @@ -101,27 +112,3 @@ class DeadshotDamageEffect extends OneShotEffect { return false; } } - -class DeadshotTargetCreaturePermanent extends TargetCreaturePermanent { - - public DeadshotTargetCreaturePermanent(FilterCreaturePermanent filter) { - super(filter); - } - - @Override - public boolean canTarget(UUID id, Ability source, Game game) { - if (source.getTargets().getFirstTarget().equals(id)) { - return false; - } - return super.canTarget(id, source, game); - } - - @Override - public boolean canTarget(UUID controllerId, UUID id, Ability source, Game game) { - if (source.getTargets().getFirstTarget().equals(id)) { - return false; - } - return super.canTarget(controllerId, id, source, game); - } - -} \ No newline at end of file diff --git a/Mage/src/mage/filter/predicate/mageobject/AnotherTargetPredicate.java b/Mage/src/mage/filter/predicate/mageobject/AnotherTargetPredicate.java new file mode 100644 index 00000000000..cd906f4a2ac --- /dev/null +++ b/Mage/src/mage/filter/predicate/mageobject/AnotherTargetPredicate.java @@ -0,0 +1,72 @@ +/* + * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ +package mage.filter.predicate.mageobject; + +import mage.MageObject; +import mage.filter.predicate.ObjectSourcePlayer; +import mage.filter.predicate.ObjectSourcePlayerPredicate; +import mage.game.Game; +import mage.game.stack.StackObject; +import mage.target.Target; + +/** + * All targets that are already selected in other target definitions of the + * source are omitted To use this predicate you have to set the targetTag of all + * targets involved in the card constructor to a unique value (e.g. using 1,2,3 + * for three targets) + * + * @author LevelX2 + */ +public class AnotherTargetPredicate implements ObjectSourcePlayerPredicate> { + + private final int targetTag; + + public AnotherTargetPredicate(int targetTag) { + this.targetTag = targetTag; + } + + @Override + public boolean apply(ObjectSourcePlayer input, Game game) { + StackObject source = game.getStack().getStackObject(input.getSourceId()); + if (source != null) { + for (Target target : source.getStackAbility().getTargets()) { + if (target.getTargetTag() > 0 // target is included in the target group to check + && target.getTargetTag() != targetTag // it's not the target of this predicate + && target.getTargets().contains(input.getObject().getId())) { // if the uuid already is used for another target in the group it's no allowed here + return false; + } + } + } + return true; + } + + @Override + public String toString() { + return "Another target"; + } +} diff --git a/Mage/src/mage/target/Target.java b/Mage/src/mage/target/Target.java index b6bd637b1e4..80b977f4eed 100644 --- a/Mage/src/mage/target/Target.java +++ b/Mage/src/mage/target/Target.java @@ -151,4 +151,8 @@ public interface Target extends Serializable { UUID getAbilityController(); Player getTargetController(Game game, UUID playerId); + + int getTargetTag(); + + void setTargetTag(int tag); } diff --git a/Mage/src/mage/target/TargetImpl.java b/Mage/src/mage/target/TargetImpl.java index ce0a8790f48..df09789ca13 100644 --- a/Mage/src/mage/target/TargetImpl.java +++ b/Mage/src/mage/target/TargetImpl.java @@ -70,6 +70,8 @@ public abstract class TargetImpl implements Target { protected UUID targetController = null; // if null the ability controller is the targetController protected UUID abilityController = null; // only used if target controller != ability controller + protected int targetTag; // can be set if other target check is needed (AnotherTargetPredicate) + @Override public abstract TargetImpl copy(); @@ -95,6 +97,7 @@ public abstract class TargetImpl implements Target { this.notTarget = target.notTarget; this.targetController = target.targetController; this.abilityController = target.abilityController; + this.targetTag = target.targetTag; } @Override @@ -545,4 +548,20 @@ public abstract class TargetImpl implements Target { return requiredExplicitlySet; } + @Override + public int getTargetTag() { + return targetTag; + } + + /** + * Is used to be able to check, that another target is slected within the + * group of targets of the ability with a target tag > 0. + * + * @param targetTag + */ + @Override + public void setTargetTag(int targetTag) { + this.targetTag = targetTag; + } + } diff --git a/Mage/src/mage/target/TargetPermanent.java b/Mage/src/mage/target/TargetPermanent.java index 222e08d0586..068bac9f0cb 100644 --- a/Mage/src/mage/target/TargetPermanent.java +++ b/Mage/src/mage/target/TargetPermanent.java @@ -1,44 +1,42 @@ /* -* Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. -* -* Redistribution and use in source and binary forms, with or without modification, are -* permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, this list of -* conditions and the following disclaimer. -* -* 2. Redistributions in binary form must reproduce the above copyright notice, this list -* of conditions and the following disclaimer in the documentation and/or other materials -* provided with the distribution. -* -* THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED -* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND -* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR -* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON -* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF -* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -* -* The views and conclusions contained in the software and documentation are those of the -* authors and should not be interpreted as representing official policies, either expressed -* or implied, of BetaSteward_at_googlemail.com. -*/ - + * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ package mage.target; import java.util.HashSet; import java.util.Set; import java.util.UUID; -import mage.constants.Zone; import mage.MageObject; import mage.abilities.Ability; +import mage.constants.Zone; import mage.filter.FilterPermanent; import mage.game.Game; import mage.game.permanent.Permanent; - /** * * @author BetaSteward_at_googlemail.com @@ -50,7 +48,7 @@ public class TargetPermanent extends TargetObject { public TargetPermanent() { this(1, 1, new FilterPermanent(), false); } - + public TargetPermanent(FilterPermanent filter) { this(1, 1, filter, false); } @@ -88,8 +86,8 @@ public class TargetPermanent extends TargetObject { // first for protection from spells or abilities (e.g. protection from colored spells, r1753) // second for protection from sources (e.g. protection from artifacts + equip ability) if (!isNotTarget()) { - if (!permanent.canBeTargetedBy(game.getObject(source.getId()), controllerId, game) || - !permanent.canBeTargetedBy(game.getObject(source.getSourceId()), controllerId, game)) { + if (!permanent.canBeTargetedBy(game.getObject(source.getId()), controllerId, game) + || !permanent.canBeTargetedBy(game.getObject(source.getSourceId()), controllerId, game)) { return false; } } @@ -117,7 +115,8 @@ public class TargetPermanent extends TargetObject { /** * Checks if there are enough {@link Permanent} that can be chosen. * - * Takes into account notTarget parameter, in case it's true doesn't check for protection, shroud etc. + * Takes into account notTarget parameter, in case it's true doesn't check + * for protection, shroud etc. * * @param sourceId the target event source * @param sourceControllerId controller of the target event source @@ -132,7 +131,7 @@ public class TargetPermanent extends TargetObject { } int count = 0; MageObject targetSource = game.getObject(sourceId); - for (Permanent permanent: game.getBattlefield().getActivePermanents(filter, sourceControllerId, sourceId, game)) { + for (Permanent permanent : game.getBattlefield().getActivePermanents(filter, sourceControllerId, sourceId, game)) { if (!targets.containsKey(permanent.getId())) { if (notTarget || permanent.canBeTargetedBy(targetSource, sourceControllerId, game)) { count++; @@ -146,9 +145,10 @@ public class TargetPermanent extends TargetObject { } /** - * Checks if there are enough {@link Permanent} that can be selected. Should not be used - * for Ability targets since this does not check for protection, shroud etc. - * + * Checks if there are enough {@link Permanent} that can be selected. Should + * not be used for Ability targets since this does not check for protection, + * shroud etc. + * * @param sourceControllerId - controller of the select event * @param game * @return - true if enough valid {@link Permanent} exist @@ -162,7 +162,7 @@ public class TargetPermanent extends TargetObject { return true; } int count = 0; - for (Permanent permanent: game.getBattlefield().getActivePermanents(filter, sourceControllerId, game)) { + for (Permanent permanent : game.getBattlefield().getActivePermanents(filter, sourceControllerId, game)) { if (!targets.containsKey(permanent.getId())) { count++; if (count >= remainingTargets) { @@ -177,7 +177,7 @@ public class TargetPermanent extends TargetObject { public Set possibleTargets(UUID sourceId, UUID sourceControllerId, Game game) { Set possibleTargets = new HashSet<>(); MageObject targetSource = game.getObject(sourceId); - for (Permanent permanent: game.getBattlefield().getActivePermanents(filter, sourceControllerId, sourceId, game)) { + for (Permanent permanent : game.getBattlefield().getActivePermanents(filter, sourceControllerId, sourceId, game)) { if (!targets.containsKey(permanent.getId())) { if (notTarget || permanent.canBeTargetedBy(targetSource, sourceControllerId, game)) { possibleTargets.add(permanent.getId()); @@ -190,7 +190,7 @@ public class TargetPermanent extends TargetObject { @Override public Set possibleTargets(UUID sourceControllerId, Game game) { Set possibleTargets = new HashSet<>(); - for (Permanent permanent: game.getBattlefield().getActivePermanents(filter, sourceControllerId, game)) { + for (Permanent permanent : game.getBattlefield().getActivePermanents(filter, sourceControllerId, game)) { if (!targets.containsKey(permanent.getId())) { possibleTargets.add(permanent.getId()); } From 7c35a69360c4a3ff03cde819b80c07cffd37f5b5 Mon Sep 17 00:00:00 2001 From: LevelX2 Date: Mon, 19 Oct 2015 22:36:28 +0200 Subject: [PATCH 07/25] * Fixed a bug that if copied spells should be shuffled into the library the original spell was removed from the stack. --- .../sets/mirrodinbesieged/RedSunsZenith.java | 14 ++-- .../cards/triggers/MelekIzzetParagonTest.java | 77 +++++++++++++++++++ .../effects/common/ShuffleSpellEffect.java | 64 ++++++++------- Mage/src/mage/game/stack/Spell.java | 3 +- Mage/src/mage/players/PlayerImpl.java | 6 +- 5 files changed, 123 insertions(+), 41 deletions(-) create mode 100644 Mage.Tests/src/test/java/org/mage/test/cards/triggers/MelekIzzetParagonTest.java diff --git a/Mage.Sets/src/mage/sets/mirrodinbesieged/RedSunsZenith.java b/Mage.Sets/src/mage/sets/mirrodinbesieged/RedSunsZenith.java index 48dec655e1c..54631fd0c1e 100644 --- a/Mage.Sets/src/mage/sets/mirrodinbesieged/RedSunsZenith.java +++ b/Mage.Sets/src/mage/sets/mirrodinbesieged/RedSunsZenith.java @@ -1,16 +1,16 @@ /* * Copyright 2011 BetaSteward_at_googlemail.com. All rights reserved. - * + * * Redistribution and use in source and binary forms, with or without modification, are * permitted provided that the following conditions are met: - * + * * 1. Redistributions of source code must retain the above copyright notice, this list of * conditions and the following disclaimer. - * + * * 2. Redistributions in binary form must reproduce the above copyright notice, this list * of conditions and the following disclaimer in the documentation and/or other materials * provided with the distribution. - * + * * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR @@ -20,7 +20,7 @@ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * + * * The views and conclusions contained in the software and documentation are those of the * authors and should not be interpreted as representing official policies, either expressed * or implied, of BetaSteward_at_googlemail.com. @@ -49,7 +49,9 @@ public class RedSunsZenith extends CardImpl { super(ownerId, 74, "Red Sun's Zenith", Rarity.RARE, new CardType[]{CardType.SORCERY}, "{X}{R}"); this.expansionSetCode = "MBS"; - + // Red Sun's Zenith deals X damage to target creature or player. + // If a creature dealt damage this way would die this turn, exile it instead. + // Shuffle Red Sun's Zenith into its owner's library. this.getSpellAbility().addTarget(new TargetCreatureOrPlayer()); this.getSpellAbility().addEffect(new DamageTargetEffect(new ManacostVariableValue())); this.getSpellAbility().addEffect(new DealtDamageToCreatureBySourceDies(this, Duration.EndOfTurn)); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/triggers/MelekIzzetParagonTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/triggers/MelekIzzetParagonTest.java new file mode 100644 index 00000000000..8560a101906 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/triggers/MelekIzzetParagonTest.java @@ -0,0 +1,77 @@ +/* + * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ +package org.mage.test.cards.triggers; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * + * @author LevelX2 + */ +public class MelekIzzetParagonTest extends CardTestPlayerBase { + + /** + * Wenn Melek, Izzet Paragon liegt und man einen Red/Blue Sun's Zenith von + * der Bib spielt, wird er nicht kopiert, auch wenn der Effekt auf dem Stack + * sichtbar ist. + * + * Meine Theorie ist, dass die Kopie beim in die Bib mischen den Originalen + * nimmt und er daher nicht mehr dem Stack ist um selbst verrechnet zu + * werden + * + */ + @Test + public void testCopyZenith() { + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 5); + // Play with the top card of your library revealed. + // You may cast the top card of your library if it's an instant or sorcery card. + // Whenever you cast an instant or sorcery spell from your library, copy it. You may choose new targets for the copy. + addCard(Zone.BATTLEFIELD, playerA, "Melek, Izzet Paragon"); + + // Red Sun's Zenith deals X damage to target creature or player. + // If a creature dealt damage this way would die this turn, exile it instead. + // Shuffle Red Sun's Zenith into its owner's library. + addCard(Zone.LIBRARY, playerA, "Red Sun's Zenith"); // {X}{R} + skipInitShuffling(); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Red Sun's Zenith", playerB); + setChoice(playerA, "X=4"); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertGraveyardCount(playerA, "Red Sun's Zenith", 0); + + assertLife(playerA, 20); + assertLife(playerB, 12); + + } +} diff --git a/Mage/src/mage/abilities/effects/common/ShuffleSpellEffect.java b/Mage/src/mage/abilities/effects/common/ShuffleSpellEffect.java index 47b4322115a..fa8e72af499 100644 --- a/Mage/src/mage/abilities/effects/common/ShuffleSpellEffect.java +++ b/Mage/src/mage/abilities/effects/common/ShuffleSpellEffect.java @@ -1,37 +1,35 @@ /* -* Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. -* -* Redistribution and use in source and binary forms, with or without modification, are -* permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, this list of -* conditions and the following disclaimer. -* -* 2. Redistributions in binary form must reproduce the above copyright notice, this list -* of conditions and the following disclaimer in the documentation and/or other materials -* provided with the distribution. -* -* THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED -* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND -* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR -* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON -* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF -* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -* -* The views and conclusions contained in the software and documentation are those of the -* authors and should not be interpreted as representing official policies, either expressed -* or implied, of BetaSteward_at_googlemail.com. -*/ - + * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ package mage.abilities.effects.common; import mage.abilities.Ability; import mage.abilities.MageSingleton; import mage.abilities.effects.OneShotEffect; -import mage.cards.Card; import mage.constants.Outcome; import mage.constants.Zone; import mage.game.Game; @@ -59,15 +57,15 @@ public class ShuffleSpellEffect extends OneShotEffect implements MageSingleton { public boolean apply(Game game, Ability source) { Player controller = game.getPlayer(source.getControllerId()); if (controller != null) { - Spell spell = game.getStack().getSpell(source.getSourceId()); + // We have to use the spell id because in case of copied spells, the sourceId can be multiple times on the stack + Spell spell = game.getStack().getSpell(source.getId()); if (spell != null) { - Card spellCard = spell.getCard(); - if (spellCard != null) { - Player owner = game.getPlayer(spellCard.getOwnerId()); + if (controller.moveCards(spell, Zone.LIBRARY, source, game) && !spell.isCopy()) { + Player owner = game.getPlayer(spell.getCard().getOwnerId()); if (owner != null) { - controller.moveCardToLibraryWithInfo(spellCard, source.getSourceId(), game, Zone.STACK, true, true); owner.shuffleLibrary(game); } + } } return true; diff --git a/Mage/src/mage/game/stack/Spell.java b/Mage/src/mage/game/stack/Spell.java index 83b99d90d46..0b0f22fb45a 100644 --- a/Mage/src/mage/game/stack/Spell.java +++ b/Mage/src/mage/game/stack/Spell.java @@ -622,7 +622,8 @@ public class Spell extends StackObjImpl implements Card { } public Spell copySpell() { - return new Spell(this.card.copy(), this.ability.copySpell(), this.controllerId, this.fromZone); + // replaced card.copy by copy (card content should no longer be changed) + return new Spell(this.card, this.ability.copySpell(), this.controllerId, this.fromZone); } @Override diff --git a/Mage/src/mage/players/PlayerImpl.java b/Mage/src/mage/players/PlayerImpl.java index 80129822440..8949f270970 100644 --- a/Mage/src/mage/players/PlayerImpl.java +++ b/Mage/src/mage/players/PlayerImpl.java @@ -3097,7 +3097,11 @@ public abstract class PlayerImpl implements Player, Serializable { break; case LIBRARY: for (Card card : cards) { - fromZone = game.getState().getZone(card.getId()); + if (card instanceof Spell) { + fromZone = game.getState().getZone(((Spell) card).getSourceId()); + } else { + fromZone = game.getState().getZone(card.getId()); + } boolean hideCard = fromZone.equals(Zone.HAND) || fromZone.equals(Zone.LIBRARY); if (moveCardToLibraryWithInfo(card, source == null ? null : source.getSourceId(), game, fromZone, true, !hideCard)) { successfulMovedCards.add(card); From 606bf4d6e0b296d21cd5ecd6bd5de828706fdc17 Mon Sep 17 00:00:00 2001 From: LevelX2 Date: Tue, 20 Oct 2015 00:44:32 +0200 Subject: [PATCH 08/25] * Fixed that it was not possible to play spells without costs with alternative costs (e.g. Ancestral Visions with Omniscience). Fixed that playing spells with alternate costs did also remove additional costs (e.g. card with entwine cast with Omniscience). --- .../src/mage/sets/onslaught/FutureSight.java | 4 +- .../keywords/SpliceOnArcaneTest.java | 2 +- .../test/cards/single/OmniscienceTest.java | 100 ++++++++++++++++++ Mage/src/mage/abilities/AbilityImpl.java | 5 +- Mage/src/mage/abilities/SpellAbility.java | 4 - .../costs/AlternativeCostSourceAbility.java | 39 ++++--- .../abilities/keyword/EntwineAbility.java | 2 +- Mage/src/mage/players/PlayerImpl.java | 26 +++-- 8 files changed, 150 insertions(+), 32 deletions(-) diff --git a/Mage.Sets/src/mage/sets/onslaught/FutureSight.java b/Mage.Sets/src/mage/sets/onslaught/FutureSight.java index 0c8dff1f1c6..929246b111d 100644 --- a/Mage.Sets/src/mage/sets/onslaught/FutureSight.java +++ b/Mage.Sets/src/mage/sets/onslaught/FutureSight.java @@ -35,7 +35,6 @@ import mage.cards.CardImpl; import mage.constants.CardType; import mage.constants.Rarity; import mage.constants.Zone; -import mage.filter.FilterCard; /** * @@ -47,11 +46,10 @@ public class FutureSight extends CardImpl { super(ownerId, 84, "Future Sight", Rarity.RARE, new CardType[]{CardType.ENCHANTMENT}, "{2}{U}{U}{U}"); this.expansionSetCode = "ONS"; - // Play with the top card of your library revealed. this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new PlayWithTheTopCardRevealedEffect())); // You may play the top card of your library. - this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new PlayTheTopCardEffect(new FilterCard()))); + this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new PlayTheTopCardEffect())); } public FutureSight(final FutureSight card) { diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/SpliceOnArcaneTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/SpliceOnArcaneTest.java index 0cd925879a7..f67350c1d85 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/SpliceOnArcaneTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/SpliceOnArcaneTest.java @@ -115,7 +115,7 @@ public class SpliceOnArcaneTest extends CardTestPlayerBase { addCard(Zone.BATTLEFIELD, playerA, "Mountain", 4); // You may exile a green card with converted mana cost X from your hand rather than pay Nourishing Shoal's mana cost. // You gain X life. - addCard(Zone.HAND, playerA, "Nourishing Shoal", 1); + addCard(Zone.HAND, playerA, "Nourishing Shoal", 1); // {X}{G}{G} addCard(Zone.HAND, playerA, "Giant Growth", 1); // You may put a creature card from your hand onto the battlefield. That creature gains haste. Sacrifice that creature at the beginning of the next end step. // Splice onto Arcane {2}{R}{R} (As you cast an Arcane spell, you may reveal this card from your hand and pay its splice cost. If you do, add this card's effects to that spell.) diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/OmniscienceTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/OmniscienceTest.java index ad4f7febe71..fe2d8ea9fb3 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/single/OmniscienceTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/OmniscienceTest.java @@ -192,4 +192,104 @@ public class OmniscienceTest extends CardTestPlayerBase { assertGraveyardCount(playerB, "Pillarfield Ox", 1); } + /** + * If another effect (e.g. Future Sight) allows you to cast nonland cards + * from zones other than your hand, Xmage incorrectly lets you cast those + * cards without paying their mana costs. Omniscience only lets you cast + * spells from your hand without paying their mana costs. + */ + @Test + public void testCastingWithFutureSight() { + // You may cast nonland cards from your hand without paying their mana costs. + addCard(Zone.BATTLEFIELD, playerA, "Omniscience"); + // Play with the top card of your library revealed. + // You may play the top card of your library. + addCard(Zone.BATTLEFIELD, playerA, "Future Sight", 1); + addCard(Zone.BATTLEFIELD, playerA, "Plains", 2); + + addCard(Zone.LIBRARY, playerA, "Silvercoat Lion", 1); + skipInitShuffling(); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Silvercoat Lion"); + setChoice(playerA, "Yes"); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertLife(playerA, 20); + assertLife(playerB, 20); + + assertPermanentCount(playerA, "Silvercoat Lion", 1); + assertTapped("Plains", true); // plains have to be tapped because {2} have to be paid + } + + /** + * If a spell has an additional cost (optional or mandatory, e.g. Entwine), + * Omniscience incorrectly allows you cast the spell as if that cost had + * been paid without paying that spell's mana cost. 117.9d If an alternative + * cost is being paid to cast a spell, any additional costs, cost increases, + * and cost reductions that affect that spell are applied to that + * alternative cost. (See rule 601.2f.) + */ + @Test + public void testCastingWithCyclonicRiftWithOverload() { + // You may cast nonland cards from your hand without paying their mana costs. + addCard(Zone.BATTLEFIELD, playerA, "Omniscience"); + addCard(Zone.BATTLEFIELD, playerA, "Plains", 2); + + // Choose one - Barbed Lightning deals 3 damage to target creature; or Barbed Lightning deals 3 damage to target player. + // Entwine {2} (Choose both if you pay the entwine cost.) + addCard(Zone.HAND, playerA, "Barbed Lightning", 1); + + // Creature - 3/3 Swampwalk + addCard(Zone.BATTLEFIELD, playerB, "Bog Wraith", 1); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Barbed Lightning", "Bog Wraith"); + addTarget(playerA, playerB); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertGraveyardCount(playerA, "Barbed Lightning", 1); + assertGraveyardCount(playerB, "Bog Wraith", 1); + + assertLife(playerA, 20); + assertLife(playerB, 17); + + assertTapped("Plains", true); // plains have to be tapped because {2} from Entwine have to be paid + } + + /** + * If a spell has an unpayable cost (e.g. Ancestral Vision, which has no + * mana cost), Omniscience should allow you to cast that spell without + * paying its mana cost. In the case of Ancestral Vision, for example, Xmage + * only gives you the option to suspend Ancestral Vision. 117.6a If an + * unpayable cost is increased by an effect or an additional cost is + * imposed, the cost is still unpayable. If an alternative cost is applied + * to an unpayable cost, including an effect that allows a player to cast a + * spell without paying its mana cost, the alternative cost may be paid. + */ + @Test + public void testCastingUnpayableCost() { + // You may cast nonland cards from your hand without paying their mana costs. + addCard(Zone.BATTLEFIELD, playerA, "Omniscience"); + + // Suspend 4-{U} + // Target player draws three cards. + addCard(Zone.HAND, playerA, "Ancestral Vision", 1); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Ancestral Vision", playerA); + addTarget(playerA, playerB); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertGraveyardCount(playerA, "Ancestral Vision", 1); + + assertHandCount(playerA, 3); + assertLife(playerA, 20); + assertLife(playerB, 20); + + } + } diff --git a/Mage/src/mage/abilities/AbilityImpl.java b/Mage/src/mage/abilities/AbilityImpl.java index d7b581515ba..659d4987fb1 100644 --- a/Mage/src/mage/abilities/AbilityImpl.java +++ b/Mage/src/mage/abilities/AbilityImpl.java @@ -471,7 +471,10 @@ public abstract class AbilityImpl implements Ability { } // controller specific alternate spell costs if (!noMana && !alternativeCostisUsed) { - if (this.getAbilityType().equals(AbilityType.SPELL)) { + if (this.getAbilityType().equals(AbilityType.SPELL) + // 117.9a Only one alternative cost can be applied to any one spell as it’s being cast. + // So an alternate spell ability can't be paid with Omniscience + && !((SpellAbility) this).getSpellAbilityType().equals(SpellAbilityType.BASE_ALTERNATE)) { for (AlternativeSourceCosts alternativeSourceCosts : controller.getAlternativeSourceCosts()) { if (alternativeSourceCosts.isAvailable(this, game)) { if (alternativeSourceCosts.askToActivateAlternativeCosts(this, game)) { diff --git a/Mage/src/mage/abilities/SpellAbility.java b/Mage/src/mage/abilities/SpellAbility.java index 1dedf4b81e7..5106b8dc61c 100644 --- a/Mage/src/mage/abilities/SpellAbility.java +++ b/Mage/src/mage/abilities/SpellAbility.java @@ -99,10 +99,6 @@ public class SpellAbility extends ActivatedAbilityImpl { && !controllerId.equals(playerId)) { return false; } - // Check if spell has no costs (not {0} mana costs), than it's not castable. E.g. for spells like Living End, that only can be cast by Suspend Ability. - if (this.getManaCosts().isEmpty() && this.getCosts().isEmpty()) { - return false; - } // Check if rule modifying events prevent to cast the spell in check playable mode if (this.isCheckPlayableMode()) { if (game.getContinuousEffects().preventedByRuleModification( diff --git a/Mage/src/mage/abilities/costs/AlternativeCostSourceAbility.java b/Mage/src/mage/abilities/costs/AlternativeCostSourceAbility.java index 5fb7fd20f90..be4893b1b45 100644 --- a/Mage/src/mage/abilities/costs/AlternativeCostSourceAbility.java +++ b/Mage/src/mage/abilities/costs/AlternativeCostSourceAbility.java @@ -29,6 +29,7 @@ package mage.abilities.costs; import java.util.Iterator; import mage.abilities.Ability; +import mage.abilities.SpellAbility; import mage.abilities.StaticAbility; import mage.abilities.condition.Condition; import mage.abilities.costs.mana.ManaCost; @@ -39,6 +40,7 @@ import mage.constants.Zone; import mage.filter.FilterCard; import mage.game.Game; import mage.players.Player; +import mage.util.CardUtil; /** * @@ -145,28 +147,39 @@ public class AlternativeCostSourceAbility extends StaticAbility implements Alter } Player player = game.getPlayer(ability.getControllerId()); if (player != null) { - Costs alternativeCosts; + Costs alternativeCostsToCheck; if (dynamicCost != null) { - alternativeCosts = new CostsImpl<>(); - alternativeCosts.add(convertToAlternativeCost(dynamicCost.getCost(ability, game))); + alternativeCostsToCheck = new CostsImpl<>(); + alternativeCostsToCheck.add(convertToAlternativeCost(dynamicCost.getCost(ability, game))); } else { - alternativeCosts = this.alternateCosts; + alternativeCostsToCheck = this.alternateCosts; } String costChoiceText; if (dynamicCost != null) { costChoiceText = dynamicCost.getText(ability, game); } else { - costChoiceText = alternativeCosts.isEmpty() ? "Cast without paying its mana cost?" : "Pay alternative costs? (" + alternativeCosts.getText() + ")"; + costChoiceText = alternativeCostsToCheck.isEmpty() ? "Cast without paying its mana cost?" : "Pay alternative costs? (" + alternativeCostsToCheck.getText() + ")"; } - if (alternativeCosts.canPay(ability, ability.getSourceId(), ability.getControllerId(), game) + if (alternativeCostsToCheck.canPay(ability, ability.getSourceId(), ability.getControllerId(), game) && player.chooseUse(Outcome.Benefit, costChoiceText, this, game)) { - ability.getManaCostsToPay().clear(); + if (ability instanceof SpellAbility) { + for (Iterator iterator = ability.getManaCostsToPay().iterator(); iterator.hasNext();) { + ManaCost manaCost = iterator.next(); + if (manaCost instanceof VariableCost) { + iterator.remove(); + } + } + CardUtil.reduceCost((SpellAbility) ability, ability.getManaCosts()); + + } else { + ability.getManaCostsToPay().clear(); + } if (!onlyMana) { ability.getCosts().clear(); } - for (Cost cost : alternativeCosts) { + for (Cost cost : alternativeCostsToCheck) { AlternativeCost2 alternateCost = (AlternativeCost2) cost; alternateCost.activate(); for (Iterator it = ((Costs) alternateCost).iterator(); it.hasNext();) { @@ -190,14 +203,14 @@ public class AlternativeCostSourceAbility extends StaticAbility implements Alter @Override public boolean isActivated(Ability source, Game game) { - Costs alternativeCosts; + Costs alternativeCostsToCheck; if (dynamicCost != null) { - alternativeCosts = new CostsImpl<>(); - alternativeCosts.add(convertToAlternativeCost(dynamicCost.getCost(source, game))); + alternativeCostsToCheck = new CostsImpl<>(); + alternativeCostsToCheck.add(convertToAlternativeCost(dynamicCost.getCost(source, game))); } else { - alternativeCosts = this.alternateCosts; + alternativeCostsToCheck = this.alternateCosts; } - for (AlternativeCost2 cost : alternativeCosts) { + for (AlternativeCost2 cost : alternativeCostsToCheck) { if (cost.isActivated(game)) { return true; } diff --git a/Mage/src/mage/abilities/keyword/EntwineAbility.java b/Mage/src/mage/abilities/keyword/EntwineAbility.java index c81d1cbd955..0604445c2a4 100644 --- a/Mage/src/mage/abilities/keyword/EntwineAbility.java +++ b/Mage/src/mage/abilities/keyword/EntwineAbility.java @@ -111,7 +111,7 @@ public class EntwineAbility extends StaticAbility implements OptionalAdditionalM if (player != null) { this.resetCosts(); if (additionalCost != null) { - if (player.chooseUse(Outcome.Benefit, new StringBuilder("Pay ").append(additionalCost.getText(false)).append(" ?").toString(), ability, game)) { + if (player.chooseUse(Outcome.Benefit, "Pay " + additionalCost.getText(false) + " ?", ability, game)) { additionalCost.activate(); for (Iterator it = ((Costs) additionalCost).iterator(); it.hasNext();) { Cost cost = (Cost) it.next(); diff --git a/Mage/src/mage/players/PlayerImpl.java b/Mage/src/mage/players/PlayerImpl.java index 8949f270970..1719603aa72 100644 --- a/Mage/src/mage/players/PlayerImpl.java +++ b/Mage/src/mage/players/PlayerImpl.java @@ -2317,15 +2317,23 @@ public abstract class PlayerImpl implements Player, Serializable { } } } - - ManaOptions abilityOptions = copy.getManaCostsToPay().getOptions(); - if (abilityOptions.size() == 0) { - return true; - } else { - for (Mana mana : abilityOptions) { - for (Mana avail : available) { - if (mana.enough(avail)) { - return true; + boolean canBeCastRegularly = true; + if (copy instanceof SpellAbility && copy.getManaCosts().isEmpty() && copy.getCosts().isEmpty()) { + // 117.6. Some mana costs contain no mana symbols. This represents an unpayable cost... + // 117.6a (...) If an alternative cost is applied to an unpayable cost, + // including an effect that allows a player to cast a spell without paying its mana cost, the alternative cost may be paid. + canBeCastRegularly = false; + } + if (canBeCastRegularly) { + ManaOptions abilityOptions = copy.getManaCostsToPay().getOptions(); + if (abilityOptions.size() == 0) { + return true; + } else { + for (Mana mana : abilityOptions) { + for (Mana avail : available) { + if (mana.enough(avail)) { + return true; + } } } } From d58288da6de565882c9045cae8afa731578c2222 Mon Sep 17 00:00:00 2001 From: LevelX2 Date: Tue, 20 Oct 2015 17:16:13 +0200 Subject: [PATCH 09/25] Some changes to RedirectionEffect class. Fixed that Harm's Way and Shaman en-Kor prevented the damage instead of only redirecting the damage. --- .../src/mage/sets/fatereforged/WildSlash.java | 8 +- .../src/mage/sets/magic2010/HarmsWay.java | 101 +++++-------- .../src/mage/sets/stronghold/NomadsEnKor.java | 2 +- .../src/mage/sets/stronghold/ShamanEnKor.java | 139 +++++------------- .../HarmsWayRedirectDamageTest.java | 53 ++++++- .../replacement/redirect/ShamenEnKorTest.java | 94 ++++++++++++ .../abilities/effects/RedirectionEffect.java | 75 +++++++--- 7 files changed, 272 insertions(+), 200 deletions(-) rename Mage.Tests/src/test/java/org/mage/test/cards/replacement/{prevent => redirect}/HarmsWayRedirectDamageTest.java (57%) create mode 100644 Mage.Tests/src/test/java/org/mage/test/cards/replacement/redirect/ShamenEnKorTest.java diff --git a/Mage.Sets/src/mage/sets/fatereforged/WildSlash.java b/Mage.Sets/src/mage/sets/fatereforged/WildSlash.java index 054006c589f..b2e5a665275 100644 --- a/Mage.Sets/src/mage/sets/fatereforged/WildSlash.java +++ b/Mage.Sets/src/mage/sets/fatereforged/WildSlash.java @@ -29,7 +29,6 @@ package mage.sets.fatereforged; import java.util.UUID; import mage.abilities.Ability; -import mage.abilities.common.SimpleStaticAbility; import mage.abilities.condition.LockedInCondition; import mage.abilities.condition.common.FerociousCondition; import mage.abilities.decorator.ConditionalContinuousRuleModifyingEffect; @@ -41,7 +40,6 @@ import mage.constants.CardType; import mage.constants.Duration; import mage.constants.Outcome; import mage.constants.Rarity; -import mage.constants.Zone; import mage.game.Game; import mage.game.events.GameEvent; import mage.target.common.TargetCreatureOrPlayer; @@ -60,12 +58,12 @@ public class WildSlash extends CardImpl { ContinuousRuleModifyingEffect effect = new DamageCantBePreventedEffect(); effect.setText("Ferocious — If you control a creature with power 4 or greater, damage can't be prevented this turn.
"); this.getSpellAbility().addEffect(new ConditionalContinuousRuleModifyingEffect(effect, - new LockedInCondition(FerociousCondition.getInstance()))); - + new LockedInCondition(FerociousCondition.getInstance()))); + // Wild Slash deals 2 damage to target creature or player. this.getSpellAbility().addEffect(new DamageTargetEffect(2)); this.getSpellAbility().addTarget(new TargetCreatureOrPlayer()); - + } public WildSlash(final WildSlash card) { diff --git a/Mage.Sets/src/mage/sets/magic2010/HarmsWay.java b/Mage.Sets/src/mage/sets/magic2010/HarmsWay.java index 12a58405a48..11cf5c7c68a 100644 --- a/Mage.Sets/src/mage/sets/magic2010/HarmsWay.java +++ b/Mage.Sets/src/mage/sets/magic2010/HarmsWay.java @@ -30,8 +30,7 @@ package mage.sets.magic2010; import java.util.UUID; import mage.MageObject; import mage.abilities.Ability; -import mage.abilities.effects.PreventionEffectData; -import mage.abilities.effects.PreventionEffectImpl; +import mage.abilities.effects.RedirectionEffect; import mage.cards.CardImpl; import mage.constants.CardType; import mage.constants.Duration; @@ -69,91 +68,65 @@ public class HarmsWay extends CardImpl { } } -class HarmsWayPreventDamageTargetEffect extends PreventionEffectImpl { - - private final TargetSource target; - +class HarmsWayPreventDamageTargetEffect extends RedirectionEffect { + + private final TargetSource damageSource; + public HarmsWayPreventDamageTargetEffect() { - super(Duration.EndOfTurn, 2, false, true); + super(Duration.EndOfTurn, 2, true); staticText = "The next 2 damage that a source of your choice would deal to you and/or permanents you control this turn is dealt to target creature or player instead"; - this.target = new TargetSource(); + this.damageSource = new TargetSource(); } public HarmsWayPreventDamageTargetEffect(final HarmsWayPreventDamageTargetEffect effect) { super(effect); - this.target = effect.target.copy(); + this.damageSource = effect.damageSource.copy(); } @Override public HarmsWayPreventDamageTargetEffect copy() { return new HarmsWayPreventDamageTargetEffect(this); } - + @Override public void init(Ability source, Game game) { - this.target.choose(Outcome.PreventDamage, source.getControllerId(), source.getSourceId(), game); + this.damageSource.choose(Outcome.PreventDamage, source.getControllerId(), source.getSourceId(), game); super.init(source, game); } @Override - public boolean replaceEvent(GameEvent event, Ability source, Game game) { - PreventionEffectData preventionData = preventDamageAction(event, source, game); - // deal damage now - if (preventionData.getPreventedDamage() > 0) { - UUID redirectTo = source.getFirstTarget(); - Permanent permanent = game.getPermanent(redirectTo); - if (permanent != null) { - game.informPlayers("Dealing " + preventionData.getPreventedDamage() + " to " + permanent.getLogName() + " instead"); - // keep the original source id as it is redirecting - permanent.damage(preventionData.getPreventedDamage(), event.getSourceId(), game, false, true); - discard(); - } - Player player = game.getPlayer(redirectTo); - if (player != null) { - game.informPlayers("Dealing " + preventionData.getPreventedDamage() + " to " + player.getLogName() + " instead"); - // keep the original source id as it is redirecting - player.damage(preventionData.getPreventedDamage(), event.getSourceId(), game, false, true); - discard(); + public boolean applies(GameEvent event, Ability source, Game game) { + // check source + MageObject object = game.getObject(event.getSourceId()); + if (object == null) { + game.informPlayers("Couldn't find source of damage"); + return false; + } + + if (!object.getId().equals(damageSource.getFirstTarget()) + && (!(object instanceof Spell) || !((Spell) object).getSourceId().equals(damageSource.getFirstTarget()))) { + return false; + } + this.redirectTarget = source.getTargets().get(0); + + // check target + // check permanent first + Permanent permanent = game.getPermanent(event.getTargetId()); + if (permanent != null) { + if (permanent.getControllerId().equals(source.getControllerId())) { + // it's your permanent + return true; } } - return false; - } - - @Override - public boolean applies(GameEvent event, Ability source, Game game) { - if (super.applies(event, source, game)) { - // check source - MageObject object = game.getObject(event.getSourceId()); - if (object == null) { - game.informPlayers("Couldn't find source of damage"); - return false; - } - - if (!object.getId().equals(target.getFirstTarget()) - && (!(object instanceof Spell) || !((Spell) object).getSourceId().equals(target.getFirstTarget()))) { - return false; - } - - // check target - // check permanent first - Permanent permanent = game.getPermanent(event.getTargetId()); - if (permanent != null) { - if (permanent.getControllerId().equals(source.getControllerId())) { - // it's your permanent - return true; - } - } - // check player - Player player = game.getPlayer(event.getTargetId()); - if (player != null) { - if (player.getId().equals(source.getControllerId())) { - // it is you - return true; - } + // check player + Player player = game.getPlayer(event.getTargetId()); + if (player != null) { + if (player.getId().equals(source.getControllerId())) { + // it is you + return true; } } return false; } } - diff --git a/Mage.Sets/src/mage/sets/stronghold/NomadsEnKor.java b/Mage.Sets/src/mage/sets/stronghold/NomadsEnKor.java index 2c2bf9a230f..c1dbfdd8b84 100644 --- a/Mage.Sets/src/mage/sets/stronghold/NomadsEnKor.java +++ b/Mage.Sets/src/mage/sets/stronghold/NomadsEnKor.java @@ -55,7 +55,7 @@ public class NomadsEnKor extends CardImpl { this.toughness = new MageInt(1); // {0}: The next 1 damage that would be dealt to Nomads en-Kor this turn is dealt to target creature you control instead. - Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new ShamanEnKorPreventionEffect(), new GenericManaCost(0)); + Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new ShamanEnKorRedirectFromItselfEffect(), new GenericManaCost(0)); ability.addTarget(new TargetControlledCreaturePermanent()); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/sets/stronghold/ShamanEnKor.java b/Mage.Sets/src/mage/sets/stronghold/ShamanEnKor.java index 97c5d5349e0..0bac73f71d5 100644 --- a/Mage.Sets/src/mage/sets/stronghold/ShamanEnKor.java +++ b/Mage.Sets/src/mage/sets/stronghold/ShamanEnKor.java @@ -29,14 +29,12 @@ package mage.sets.stronghold; import java.util.UUID; import mage.MageInt; -import mage.MageObject; +import mage.MageObjectReference; import mage.abilities.Ability; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.costs.mana.GenericManaCost; import mage.abilities.costs.mana.ManaCostsImpl; -import mage.abilities.effects.PreventionEffectData; -import mage.abilities.effects.PreventionEffectImpl; -import mage.abilities.effects.ReplacementEffectImpl; +import mage.abilities.effects.RedirectionEffect; import mage.cards.CardImpl; import mage.constants.CardType; import mage.constants.Duration; @@ -44,11 +42,10 @@ import mage.constants.Outcome; import mage.constants.Rarity; import mage.constants.Zone; import mage.game.Game; -import mage.game.events.DamageEvent; import mage.game.events.GameEvent; import mage.game.events.GameEvent.EventType; -import mage.game.permanent.Permanent; import mage.players.Player; +import mage.target.TargetPermanent; import mage.target.TargetSource; import mage.target.common.TargetControlledCreaturePermanent; import mage.target.common.TargetCreaturePermanent; @@ -70,12 +67,12 @@ public class ShamanEnKor extends CardImpl { this.toughness = new MageInt(2); // {0}: The next 1 damage that would be dealt to Shaman en-Kor this turn is dealt to target creature you control instead. - Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new ShamanEnKorPreventionEffect(), new GenericManaCost(0)); + Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new ShamanEnKorRedirectFromItselfEffect(), new GenericManaCost(0)); ability.addTarget(new TargetControlledCreaturePermanent()); this.addAbility(ability); - + // {1}{W}: The next time a source of your choice would deal damage to target creature this turn, that damage is dealt to Shaman en-Kor instead. - ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new ShamanEnKorReplacementEffect(), new ManaCostsImpl("{1}{W}")); + ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new ShamanEnKorRedirectFromTargetEffect(), new ManaCostsImpl("{1}{W}")); ability.addTarget(new TargetCreaturePermanent()); this.addAbility(ability); } @@ -90,129 +87,69 @@ public class ShamanEnKor extends CardImpl { } } -class ShamanEnKorPreventionEffect extends PreventionEffectImpl { - - ShamanEnKorPreventionEffect() { - super(Duration.EndOfTurn, 1, false); +class ShamanEnKorRedirectFromItselfEffect extends RedirectionEffect { + + ShamanEnKorRedirectFromItselfEffect() { + super(Duration.EndOfTurn, 1, true); staticText = "The next 1 damage that would be dealt to {this} this turn is dealt to target creature you control instead."; } - - ShamanEnKorPreventionEffect(final ShamanEnKorPreventionEffect effect) { + + ShamanEnKorRedirectFromItselfEffect(final ShamanEnKorRedirectFromItselfEffect effect) { super(effect); } - + @Override - public ShamanEnKorPreventionEffect copy() { - return new ShamanEnKorPreventionEffect(this); + public ShamanEnKorRedirectFromItselfEffect copy() { + return new ShamanEnKorRedirectFromItselfEffect(this); } - - @Override - public boolean replaceEvent(GameEvent event, Ability source, Game game) { - PreventionEffectData preventionResult = preventDamageAction(event, source, game); - if (preventionResult.getPreventedDamage() > 0) { - Permanent redirectTo = game.getPermanent(getTargetPointer().getFirst(game, source)); - if (redirectTo != null) { - game.informPlayers("Dealing " + preventionResult.getPreventedDamage() + " to " + redirectTo.getName() + " instead."); - DamageEvent damageEvent = (DamageEvent) event; - redirectTo.damage(preventionResult.getPreventedDamage(), event.getSourceId(), game, damageEvent.isCombatDamage(), damageEvent.isPreventable(), event.getAppliedEffects()); - } - } - return false; - } - + @Override public boolean applies(GameEvent event, Ability source, Game game) { - if (!this.used && super.applies(event, source, game)) { - if (event.getTargetId().equals(source.getSourceId())) { - return game.getPermanent(getTargetPointer().getFirst(game, source)) != null; - } + if (event.getTargetId().equals(source.getSourceId())) { + this.redirectTarget = source.getTargets().get(0); + return true; } return false; } } -class ShamanEnKorReplacementEffect extends ReplacementEffectImpl { - - protected TargetSource targetSource; +class ShamanEnKorRedirectFromTargetEffect extends RedirectionEffect { - ShamanEnKorReplacementEffect() { - super(Duration.EndOfTurn, Outcome.RedirectDamage); + protected MageObjectReference sourceObject; + + ShamanEnKorRedirectFromTargetEffect() { + super(Duration.EndOfTurn, Integer.MAX_VALUE, true); staticText = "The next time a source of your choice would deal damage to target creature this turn, that damage is dealt to {this} instead"; } - ShamanEnKorReplacementEffect(final ShamanEnKorReplacementEffect effect) { + ShamanEnKorRedirectFromTargetEffect(final ShamanEnKorRedirectFromTargetEffect effect) { super(effect); - targetSource = effect.targetSource; + sourceObject = effect.sourceObject; } @Override public void init(Ability source, Game game) { Player player = game.getPlayer(source.getControllerId()); - TargetSource target = new TargetSource(); - target.setNotTarget(true); if (player != null) { + TargetSource target = new TargetSource(); target.choose(Outcome.PreventDamage, player.getId(), source.getSourceId(), game); - this.targetSource = target; + this.sourceObject = new MageObjectReference(target.getFirstTarget(), game); + } else { + discard(); } } - + @Override public boolean checksEventType(GameEvent event, Game game) { return event.getType() == EventType.DAMAGE_CREATURE; } - - @Override - public boolean applies(GameEvent event, Ability source, Game game) { - if (!this.used) { - if (targetSource != null) { - if (event.getSourceId().equals(targetSource.getFirstTarget())) { - // check source - MageObject object = game.getObject(event.getSourceId()); - if (object == null) { - game.informPlayers("Couldn't find source of damage"); - return false; - } - else { - if (event.getTargetId().equals(source.getFirstTarget())) { - Permanent permanent = game.getPermanent(source.getFirstTarget()); - if (permanent != null) { - return true; - } - } - } - } - } - } - return false; - } @Override - public boolean replaceEvent(GameEvent event, Ability source, Game game) { - DamageEvent damageEvent = (DamageEvent)event; - Permanent sourcePermanent = game.getPermanent(source.getSourceId()); - if (sourcePermanent != null) { - // get name of old target - Permanent targetPermanent = game.getPermanent(event.getTargetId()); - StringBuilder message = new StringBuilder(); - message.append(sourcePermanent.getName()).append(": gets "); - message.append(damageEvent.getAmount()).append(" damage redirected from "); - if (targetPermanent != null) { - message.append(targetPermanent.getName()); - } - else { - Player targetPlayer = game.getPlayer(event.getTargetId()); - if (targetPlayer != null) { - message.append(targetPlayer.getLogName()); - } - else { - message.append("unknown"); - } - } - game.informPlayers(message.toString()); - // redirect damage - this.used = true; - sourcePermanent.damage(damageEvent.getAmount(), damageEvent.getSourceId(), game, damageEvent.isCombatDamage(), damageEvent.isPreventable(), event.getAppliedEffects()); - return true; + public boolean applies(GameEvent event, Ability source, Game game) { + if (sourceObject.equals(new MageObjectReference(event.getSourceId(), game))) { + redirectTarget = new TargetPermanent(); + redirectTarget.add(source.getSourceId(), game); + return event.getTargetId().equals(getTargetPointer().getFirst(game, source)); } return false; } @@ -223,7 +160,7 @@ class ShamanEnKorReplacementEffect extends ReplacementEffectImpl { } @Override - public ShamanEnKorReplacementEffect copy() { - return new ShamanEnKorReplacementEffect(this); + public ShamanEnKorRedirectFromTargetEffect copy() { + return new ShamanEnKorRedirectFromTargetEffect(this); } } diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/replacement/prevent/HarmsWayRedirectDamageTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/replacement/redirect/HarmsWayRedirectDamageTest.java similarity index 57% rename from Mage.Tests/src/test/java/org/mage/test/cards/replacement/prevent/HarmsWayRedirectDamageTest.java rename to Mage.Tests/src/test/java/org/mage/test/cards/replacement/redirect/HarmsWayRedirectDamageTest.java index f609e5ed8e8..7f7508a5ab6 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/replacement/prevent/HarmsWayRedirectDamageTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/replacement/redirect/HarmsWayRedirectDamageTest.java @@ -1,4 +1,4 @@ -package org.mage.test.cards.replacement.prevent; +package org.mage.test.cards.replacement.redirect; import mage.constants.PhaseStep; import mage.constants.Zone; @@ -6,15 +6,17 @@ import org.junit.Test; import org.mage.test.serverside.base.CardTestPlayerBase; /** - * Harm's Way: - * The next 2 damage that a source of your choice would deal to you and/or permanents you control this turn is dealt to target creature or player instead. + * Harm's Way: The next 2 damage that a source of your choice would deal to you + * and/or permanents you control this turn is dealt to target creature or player + * instead. * * @author noxx */ public class HarmsWayRedirectDamageTest extends CardTestPlayerBase { /** - * Tests that 2 of 3 damage is redirected while 1 damage is still dealt to original target + * Tests that 2 of 3 damage is redirected while 1 damage is still dealt to + * original target */ @Test public void testRedirectTwoDamage() { @@ -51,7 +53,7 @@ public class HarmsWayRedirectDamageTest extends CardTestPlayerBase { attack(2, playerB, "Craw Wurm"); castSpell(2, PhaseStep.DECLARE_BLOCKERS, playerA, "Harm's Way", playerB); setChoice(playerA, "Craw Wurm"); - + setStopAt(2, PhaseStep.END_TURN); execute(); @@ -79,8 +81,11 @@ public class HarmsWayRedirectDamageTest extends CardTestPlayerBase { addCard(Zone.BATTLEFIELD, playerB, "Magma Phoenix"); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Harm's Way", playerB); - setChoice(playerA, "Magma Phoenix"); - /** When Magma Phoenix dies, Magma Phoenix deals 3 damage to each creature and each player **/ + setChoice(playerA, "Magma Phoenix"); + /** + * When Magma Phoenix dies, Magma Phoenix deals 3 damage to each + * creature and each player * + */ castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Lightning Bolt", "Magma Phoenix"); setStopAt(1, PhaseStep.POSTCOMBAT_MAIN); @@ -91,4 +96,38 @@ public class HarmsWayRedirectDamageTest extends CardTestPlayerBase { assertLife(playerB, 15); // 3 damage from dying Phoenix directly and 2 redirected damage from playerA } + /** + * Tests that not preventable damage is redirected + */ + @Test + public void testRedirectNotPreventableDamage() { + addCard(Zone.BATTLEFIELD, playerA, "Mountain"); + // Ferocious If you control a creature with power 4 or greater, damage can't be prevented this turn. + // Wild Slash deals 2 damage to target creature or player. + addCard(Zone.HAND, playerA, "Wild Slash"); // {R} + addCard(Zone.BATTLEFIELD, playerA, "Serra Angel"); + addCard(Zone.BATTLEFIELD, playerA, "Silvercoat Lion"); + + // The next 2 damage that a source of your choice would deal to you and/or permanents + // you control this turn is dealt to target creature or player instead. + addCard(Zone.HAND, playerB, "Harm's Way"); // {W} + addCard(Zone.BATTLEFIELD, playerB, "Plains"); + addCard(Zone.BATTLEFIELD, playerB, "Birds of Paradise"); + + // the 2 damage can't be prevented and have to be redirected to Silvercoat Lion of player A + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Wild Slash", "Birds of Paradise"); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, "Harm's Way", "Silvercoat Lion", "Wild Slash"); + setChoice(playerB, "Wild Slash"); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertGraveyardCount(playerA, "Wild Slash", 1); + assertGraveyardCount(playerB, "Harm's Way", 1); + assertPermanentCount(playerB, "Birds of Paradise", 1); + assertGraveyardCount(playerA, "Silvercoat Lion", 1); + assertLife(playerA, 20); + assertLife(playerB, 20); + } + } diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/replacement/redirect/ShamenEnKorTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/replacement/redirect/ShamenEnKorTest.java new file mode 100644 index 00000000000..a8ffe681e75 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/replacement/redirect/ShamenEnKorTest.java @@ -0,0 +1,94 @@ +/* + * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ +package org.mage.test.cards.replacement.redirect; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * + * @author LevelX2 + */ +public class ShamenEnKorTest extends CardTestPlayerBase { + + /** + * Tests that 2 of 3 damage is redirected while 1 damage is still dealt to + * original target + */ + @Test + public void testFirstAbilityNonCombatDamage() { + addCard(Zone.HAND, playerA, "Lightning Bolt"); + addCard(Zone.BATTLEFIELD, playerA, "Mountain"); + + // {0}: The next 1 damage that would be dealt to Shaman en-Kor this turn is dealt to target creature you control instead. + // {1}{W}: The next time a source of your choice would deal damage to target creature this turn, that damage is dealt to Shaman en-Kor instead. + addCard(Zone.BATTLEFIELD, playerB, "Shaman en-Kor"); // 1/2 + addCard(Zone.BATTLEFIELD, playerB, "Silvercoat Lion"); // 2/2 + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Lightning Bolt", "Shaman en-Kor"); + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerB, "{0}: The next 1 damage", "Silvercoat Lion", "Lightning Bolt"); + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerB, "{0}: The next 1 damage", "Silvercoat Lion", "Lightning Bolt"); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertGraveyardCount(playerA, "Lightning Bolt", 1); + assertPermanentCount(playerB, "Shaman en-Kor", 1); + assertGraveyardCount(playerB, "Silvercoat Lion", 1); + + } + + @Test + public void testSecondAbilityNonCombatDamage() { + addCard(Zone.HAND, playerA, "Lightning Bolt"); + addCard(Zone.BATTLEFIELD, playerA, "Mountain"); + + // {0}: The next 1 damage that would be dealt to Shaman en-Kor this turn is dealt to target creature you control instead. + // {1}{W}: The next time a source of your choice would deal damage to target creature this turn, that damage is dealt to Shaman en-Kor instead. + addCard(Zone.BATTLEFIELD, playerB, "Shaman en-Kor"); // 1/2 + addCard(Zone.BATTLEFIELD, playerB, "Plains", 2); + addCard(Zone.BATTLEFIELD, playerB, "Silvercoat Lion"); // 2/2 + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Lightning Bolt", "Silvercoat Lion"); + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerB, "{1}{W}: The next time", "Silvercoat Lion", "Lightning Bolt"); + setChoice(playerB, "Lightning Bolt"); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertGraveyardCount(playerA, "Lightning Bolt", 1); + assertPermanentCount(playerB, "Silvercoat Lion", 1); + assertGraveyardCount(playerB, "Shaman en-Kor", 1); + + } + +} diff --git a/Mage/src/mage/abilities/effects/RedirectionEffect.java b/Mage/src/mage/abilities/effects/RedirectionEffect.java index 6c339460edf..b30400e656e 100644 --- a/Mage/src/mage/abilities/effects/RedirectionEffect.java +++ b/Mage/src/mage/abilities/effects/RedirectionEffect.java @@ -1,16 +1,16 @@ /* * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. - * + * * Redistribution and use in source and binary forms, with or without modification, are * permitted provided that the following conditions are met: - * + * * 1. Redistributions of source code must retain the above copyright notice, this list of * conditions and the following disclaimer. - * + * * 2. Redistributions in binary form must reproduce the above copyright notice, this list * of conditions and the following disclaimer in the documentation and/or other materials * provided with the distribution. - * + * * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR @@ -20,14 +20,14 @@ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * + * * The views and conclusions contained in the software and documentation are those of the * authors and should not be interpreted as representing official policies, either expressed * or implied, of BetaSteward_at_googlemail.com. */ - package mage.abilities.effects; +import mage.MageObject; import mage.abilities.Ability; import mage.constants.Duration; import mage.constants.EffectType; @@ -46,36 +46,67 @@ import mage.target.Target; public abstract class RedirectionEffect extends ReplacementEffectImpl { protected Target redirectTarget; + protected int amountToRedirect; + protected boolean oneUsage; public RedirectionEffect(Duration duration) { + this(duration, Integer.MAX_VALUE, false); + } + + public RedirectionEffect(Duration duration, int amountToRedirect, boolean oneUsage) { super(duration, Outcome.RedirectDamage); this.effectType = EffectType.REDIRECTION; + this.amountToRedirect = amountToRedirect; + this.oneUsage = oneUsage; } public RedirectionEffect(final RedirectionEffect effect) { super(effect); this.redirectTarget = effect.redirectTarget; + this.amountToRedirect = effect.amountToRedirect; + this.oneUsage = effect.oneUsage; } @Override - public boolean apply(Game game, Ability source) { - return true; - } - - @Override - public boolean replaceEvent(GameEvent event, Ability source, Game game) { - DamageEvent damageEvent = (DamageEvent)event; - Permanent permanent = game.getPermanent(redirectTarget.getFirstTarget()); - if (permanent != null) { - permanent.damage(damageEvent.getAmount(), event.getSourceId(), game, damageEvent.isCombatDamage(), damageEvent.isPreventable(), event.getAppliedEffects()); - return true; - } - Player player = game.getPlayer(redirectTarget.getFirstTarget()); - if (player != null) { - player.damage(damageEvent.getAmount(), event.getSourceId(), game, damageEvent.isCombatDamage(), damageEvent.isPreventable(), event.getAppliedEffects()); - return true; + public boolean checksEventType(GameEvent event, Game game) { + switch (event.getType()) { + case DAMAGE_CREATURE: + case DAMAGE_PLAYER: + case DAMAGE_PLANESWALKER: + return true; } return false; } + @Override + public boolean replaceEvent(GameEvent event, Ability source, Game game) { + MageObject sourceObject = game.getObject(source.getSourceId()); + DamageEvent damageEvent = (DamageEvent) event; + int restDamage = 0; + int damageToRedirect = event.getAmount(); + if (damageEvent.getAmount() > amountToRedirect) { + restDamage = damageEvent.getAmount() - amountToRedirect; + damageToRedirect = amountToRedirect; + } + if (damageToRedirect > 0 && oneUsage) { + this.discard(); + } + Permanent permanent = game.getPermanent(redirectTarget.getFirstTarget()); + if (permanent != null) { + permanent.damage(damageToRedirect, event.getSourceId(), game, damageEvent.isCombatDamage(), damageEvent.isPreventable(), event.getAppliedEffects()); + game.informPlayers(sourceObject.getLogName() + ": Redirected " + damageToRedirect + " damage to " + permanent.getLogName()); + } else { + Player player = game.getPlayer(redirectTarget.getFirstTarget()); + if (player != null) { + player.damage(damageToRedirect, event.getSourceId(), game, damageEvent.isCombatDamage(), damageEvent.isPreventable(), event.getAppliedEffects()); + game.informPlayers(sourceObject.getLogName() + ": Redirected " + damageToRedirect + " damage to " + player.getLogName()); + } + } + if (restDamage > 0) { + damageEvent.setAmount(restDamage); + return false; + } + return true; + } + } From bf2992fc42a3181b753d8b5c0b19f1fc062a09a3 Mon Sep 17 00:00:00 2001 From: fireshoes Date: Tue, 20 Oct 2015 14:52:39 -0500 Subject: [PATCH 10/25] Added Commander 2015 set; added Eternal Witness and Kalemne, Disciple of Iroas cards to C15. --- .../dl/sources/MagicCardsImageSource.java | 1 + .../dl/sources/WizardCardsImageSource.java | 1 + .../src/main/resources/image.url.properties | 4 +- Mage.Sets/src/mage/sets/Commander2015.java | 52 +++++++ .../sets/commander2015/EternalWitness.java | 52 +++++++ .../commander2015/KalemneDiscipleOfIroas.java | 129 ++++++++++++++++++ Mage/src/mage/counters/CounterType.java | 1 + Utils/known-sets.txt | 1 + Utils/mtg-cards-data.txt | 2 + Utils/mtg-sets-data.txt | 1 + 10 files changed, 242 insertions(+), 2 deletions(-) create mode 100644 Mage.Sets/src/mage/sets/Commander2015.java create mode 100644 Mage.Sets/src/mage/sets/commander2015/EternalWitness.java create mode 100644 Mage.Sets/src/mage/sets/commander2015/KalemneDiscipleOfIroas.java diff --git a/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/MagicCardsImageSource.java b/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/MagicCardsImageSource.java index 23c02863284..68877f48eb8 100644 --- a/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/MagicCardsImageSource.java +++ b/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/MagicCardsImageSource.java @@ -16,6 +16,7 @@ public class MagicCardsImageSource implements CardImageSource { private static final Map setNameTokenReplacement = new HashMap() { { + put("C15", "commander-2015"); put("ORG", "oath-of-the-gatewatch"); put("EXP", "zendikar-expeditions"); put("BFZ", "battle-for-zendikar"); diff --git a/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/WizardCardsImageSource.java b/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/WizardCardsImageSource.java index 3e1ed8c95b3..9a1c93babe8 100644 --- a/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/WizardCardsImageSource.java +++ b/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/WizardCardsImageSource.java @@ -99,6 +99,7 @@ public class WizardCardsImageSource implements CardImageSource { setsAliases.put("BTD", "Beatdown Box Set"); setsAliases.put("C13", "Commander 2013 Edition"); setsAliases.put("C14", "Commander 2014"); + setsAliases.put("C15", "Commander 2015"); setsAliases.put("CHK", "Champions of Kamigawa"); setsAliases.put("CHR", "Chronicles"); setsAliases.put("CMD", "Magic: The Gathering-Commander"); diff --git a/Mage.Client/src/main/resources/image.url.properties b/Mage.Client/src/main/resources/image.url.properties index fc6e7f06ec3..3617a698f38 100644 --- a/Mage.Client/src/main/resources/image.url.properties +++ b/Mage.Client/src/main/resources/image.url.properties @@ -64,6 +64,6 @@ ddd=gvl unh=uh dde=pvc # Remove setname as soon as the images can be downloaded -ignore.urls=TOK, OGW +ignore.urls=TOK, OGW, C15 # sets ordered by release time (newest goes first) -token.lookup.order=OGW,EXP,DDP,BFZ,FVD,FVE,FVL,FVR,V12,V13,V14,V15,TPR,MPRP,DD3,DDO,ORI,MM2,PTC,DTK,FRF,KTK,M15,VMA,CNS,JOU,BNG,THS,DDL,M14,MMA,DGM,GTC,RTR,M13,AVR,DDI,DKA,ISD,M12,NPH,MBS,SOM,M11,ROE,DDE,WWK,ZEN,M10,GVL,ARB,DVD,CFX,JVC,ALA,EVE,SHM,EVG,MOR,LRW,10E,CLS,CHK,GRC \ No newline at end of file +token.lookup.order=C15,OGW,EXP,DDP,BFZ,FVD,FVE,FVL,FVR,V12,V13,V14,V15,TPR,MPRP,DD3,DDO,ORI,MM2,PTC,DTK,FRF,KTK,M15,VMA,CNS,JOU,BNG,THS,DDL,M14,MMA,DGM,GTC,RTR,M13,AVR,DDI,DKA,ISD,M12,NPH,MBS,SOM,M11,ROE,DDE,WWK,ZEN,M10,GVL,ARB,DVD,CFX,JVC,ALA,EVE,SHM,EVG,MOR,LRW,10E,CLS,CHK,GRC \ No newline at end of file diff --git a/Mage.Sets/src/mage/sets/Commander2015.java b/Mage.Sets/src/mage/sets/Commander2015.java new file mode 100644 index 00000000000..d1069bda220 --- /dev/null +++ b/Mage.Sets/src/mage/sets/Commander2015.java @@ -0,0 +1,52 @@ +/* + * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ +package mage.sets; + +import java.util.GregorianCalendar; +import mage.cards.ExpansionSet; +import mage.constants.SetType; + +/** + * + * @author fireshoes + */ + +public class Commander2015 extends ExpansionSet { + + private static final Commander2015 fINSTANCE = new Commander2015(); + + public static Commander2015 getInstance() { + return fINSTANCE; + } + + private Commander2015() { + super("Commander 2015 Edition", "C15", "mage.sets.commander2015", new GregorianCalendar(2015, 11, 13).getTime(), SetType.SUPPLEMENTAL); + this.blockName = "Command Zone"; + } + +} diff --git a/Mage.Sets/src/mage/sets/commander2015/EternalWitness.java b/Mage.Sets/src/mage/sets/commander2015/EternalWitness.java new file mode 100644 index 00000000000..6d8b8f5065b --- /dev/null +++ b/Mage.Sets/src/mage/sets/commander2015/EternalWitness.java @@ -0,0 +1,52 @@ +/* + * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ +package mage.sets.commander2015; + +import java.util.UUID; + +/** + * + * @author fireshoes + */ +public class EternalWitness extends mage.sets.fifthdawn.EternalWitness { + + public EternalWitness(UUID ownerId) { + super(ownerId); + this.cardNumber = 183; + this.expansionSetCode = "C15"; + } + + public EternalWitness(final EternalWitness card) { + super(card); + } + + @Override + public EternalWitness copy() { + return new EternalWitness(this); + } +} diff --git a/Mage.Sets/src/mage/sets/commander2015/KalemneDiscipleOfIroas.java b/Mage.Sets/src/mage/sets/commander2015/KalemneDiscipleOfIroas.java new file mode 100644 index 00000000000..bd681bd53b1 --- /dev/null +++ b/Mage.Sets/src/mage/sets/commander2015/KalemneDiscipleOfIroas.java @@ -0,0 +1,129 @@ +/* + * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ +package mage.sets.commander2015; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.common.SpellCastControllerTriggeredAbility; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.effects.Effect; +import mage.abilities.effects.common.continuous.BoostSourceEffect; +import mage.abilities.effects.common.counter.AddCountersControllerEffect; +import mage.abilities.keyword.DoubleStrikeAbility; +import mage.abilities.keyword.VigilanceAbility; +import mage.cards.CardImpl; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.Rarity; +import mage.constants.Zone; +import mage.counters.CounterType; +import mage.filter.Filter; +import mage.filter.FilterSpell; +import mage.filter.predicate.mageobject.CardTypePredicate; +import mage.filter.predicate.mageobject.ConvertedManaCostPredicate; +import mage.game.Game; +import mage.players.Player; + +/** + * + * @author fireshoes + */ +public class KalemneDiscipleOfIroas extends CardImpl { + + private static final FilterSpell filterSpell = new FilterSpell("a creature spell with converted mana cost 5 or greater"); + + static { + filterSpell.add(new CardTypePredicate(CardType.CREATURE)); + filterSpell.add(new ConvertedManaCostPredicate(Filter.ComparisonType.GreaterThan, 4)); + } + + public KalemneDiscipleOfIroas(UUID ownerId) { + super(ownerId, 999, "Kalemne, Disciple of Iroas", Rarity.MYTHIC, new CardType[]{CardType.CREATURE}, "{2}{R}{W}"); + this.expansionSetCode = "C15"; + this.supertype.add("Legendary"); + this.subtype.add("Giant"); + this.subtype.add("Soldier"); + this.power = new MageInt(3); + this.toughness = new MageInt(3); + + // Double strike + this.addAbility(DoubleStrikeAbility.getInstance()); + + // Vigilance + this.addAbility(VigilanceAbility.getInstance()); + + // Whenever you cast a creature spell with converted mana cost 5 or greater, you get an experience counter. + Effect effect = new AddCountersControllerEffect(CounterType.EXPERIENCE.createInstance(1), false); + effect.setText("you get an experience counter"); + Ability ability = new SpellCastControllerTriggeredAbility(effect, filterSpell, false); + this.addAbility(ability); + + // Kalemne, Disciple of Iroas gets +1/+1 for each experience counter you have. + DynamicValue value = new SourceControllerExperienceCountersCount(); + this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new BoostSourceEffect(value, value, Duration.WhileOnBattlefield))); + } + + public KalemneDiscipleOfIroas(final KalemneDiscipleOfIroas card) { + super(card); + } + + @Override + public KalemneDiscipleOfIroas copy() { + return new KalemneDiscipleOfIroas(this); + } +} + +class SourceControllerExperienceCountersCount implements DynamicValue { + + @Override + public int calculate(Game game, Ability sourceAbility, Effect effect) { + int amount = 0; + Player player = game.getPlayer(sourceAbility.getControllerId()); + if (player != null) { + amount = player.getCounters().getCount(CounterType.EXPERIENCE); + } + return amount; + } + + @Override + public DynamicValue copy() { + return new SourceControllerExperienceCountersCount(); + } + + @Override + public String toString() { + return "1"; + } + + @Override + public String getMessage() { + return "experience counter you have"; + } +} diff --git a/Mage/src/mage/counters/CounterType.java b/Mage/src/mage/counters/CounterType.java index f7537638758..907e0cc8526 100644 --- a/Mage/src/mage/counters/CounterType.java +++ b/Mage/src/mage/counters/CounterType.java @@ -53,6 +53,7 @@ public enum CounterType { DOOM("doom"), ELIXIR("elixir"), EON("eon"), + EXPERIENCE("experience"), EYEBALL("eyeball"), FADE("fade"), FATE("fate"), diff --git a/Utils/known-sets.txt b/Utils/known-sets.txt index 9b462e003ae..a63078e0d91 100644 --- a/Utils/known-sets.txt +++ b/Utils/known-sets.txt @@ -14,6 +14,7 @@ Classic Sixth Edition|classicsixthedition| Coldsnap|coldsnap| Commander 2013 Edition|commander2013| Commander 2014 Edition|commander2014| +Commander 2015|commander2015| Conflux|conflux| Dark Ascension|darkascension| Darksteel|darksteel| diff --git a/Utils/mtg-cards-data.txt b/Utils/mtg-cards-data.txt index 9146198e3b3..5845fd2d74b 100644 --- a/Utils/mtg-cards-data.txt +++ b/Utils/mtg-cards-data.txt @@ -27631,3 +27631,5 @@ Swamp|Battle for Zendikar|262|L||Basic Land - Swamp|||({t}: Add {B} to your m Swamp|Battle for Zendikar|264|L||Basic Land - Swamp|||({t}: Add {B} to your mana pool.)| Swamp|Battle for Zendikar|260|L||Basic Land - Swamp|||({t}: Add {B} to your mana pool.)| Swamp|Battle for Zendikar|261|L||Basic Land - Swamp|||({t}: Add {B} to your mana pool.)| +Eternal Witness|Commander 2015|183|U|{1}{G}{G}|Creature - Human Shaman|2|1|When Eternal Witness enters the battlefield, you may return target card from your graveyard to your hand.| +Kalemne, Disciple of Iroas|Commander 2015|999|M|{2}{R}{W}|Legendary Creature - Giant Soldier|3|3|Double strike, vigilance$Whenever you cast a creature spell with converted mana cost 5 or greater, you get an experience counter.$Kalemne, Disciple of Iroas gets +1/+1 for each experience counter you have.| \ No newline at end of file diff --git a/Utils/mtg-sets-data.txt b/Utils/mtg-sets-data.txt index 83e479e4585..83318ee3973 100644 --- a/Utils/mtg-sets-data.txt +++ b/Utils/mtg-sets-data.txt @@ -25,6 +25,7 @@ Chronicles|CHR| Clash Pack|CLASH| Commander 2013 Edition|C13| Commander 2014 Edition|C14| +Commander 2015|C15| Conflux|CON| Coldsnap|CSP| Dark Ascension|DKA| From 701a722904ce191689ccff71cc7c0b35bcc6dc59 Mon Sep 17 00:00:00 2001 From: LevelX2 Date: Tue, 20 Oct 2015 22:58:24 +0200 Subject: [PATCH 11/25] * Fixed some redirect effect sthat were implemented as prevention effects (fixes #1216). --- .../sets/betrayersofkamigawa/WardOfPiety.java | 42 ++++------- .../sets/championsofkamigawa/VassalsDuty.java | 40 +++-------- .../sets/planechase/RaziaBorosArchangel.java | 66 +++++++---------- .../src/mage/sets/stronghold/NomadsEnKor.java | 6 +- .../src/mage/sets/stronghold/ShamanEnKor.java | 30 +------- .../sets/tempestremastered/SpiritEnKor.java | 53 ++------------ .../sets/tempestremastered/WarriorEnKor.java | 51 +------------ .../replacement/redirect/WardOfPietyTest.java | 71 +++++++++++++++++++ ...edirectDamageFromSourceToTargetEffect.java | 42 +++++++++++ 9 files changed, 176 insertions(+), 225 deletions(-) create mode 100644 Mage.Tests/src/test/java/org/mage/test/cards/replacement/redirect/WardOfPietyTest.java create mode 100644 Mage/src/mage/abilities/effects/common/RedirectDamageFromSourceToTargetEffect.java diff --git a/Mage.Sets/src/mage/sets/betrayersofkamigawa/WardOfPiety.java b/Mage.Sets/src/mage/sets/betrayersofkamigawa/WardOfPiety.java index 1edee8058ed..94b13e99c31 100644 --- a/Mage.Sets/src/mage/sets/betrayersofkamigawa/WardOfPiety.java +++ b/Mage.Sets/src/mage/sets/betrayersofkamigawa/WardOfPiety.java @@ -28,11 +28,11 @@ package mage.sets.betrayersofkamigawa; import java.util.UUID; +import mage.MageObjectReference; import mage.abilities.Ability; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.costs.mana.ManaCostsImpl; -import mage.abilities.effects.PreventionEffectData; -import mage.abilities.effects.PreventionEffectImpl; +import mage.abilities.effects.RedirectionEffect; import mage.abilities.effects.common.AttachEffect; import mage.abilities.keyword.EnchantAbility; import mage.cards.CardImpl; @@ -44,7 +44,6 @@ import mage.constants.Zone; import mage.game.Game; import mage.game.events.GameEvent; import mage.game.permanent.Permanent; -import mage.players.Player; import mage.target.TargetPermanent; import mage.target.common.TargetCreatureOrPlayer; import mage.target.common.TargetCreaturePermanent; @@ -60,7 +59,6 @@ public class WardOfPiety extends CardImpl { this.expansionSetCode = "BOK"; this.subtype.add("Aura"); - // Enchant creature TargetPermanent auraTarget = new TargetCreaturePermanent(); this.getSpellAbility().addTarget(auraTarget); @@ -84,10 +82,12 @@ public class WardOfPiety extends CardImpl { } } -class WardOfPietyPreventDamageTargetEffect extends PreventionEffectImpl { +class WardOfPietyPreventDamageTargetEffect extends RedirectionEffect { + + protected MageObjectReference redirectToObject; public WardOfPietyPreventDamageTargetEffect() { - super(Duration.EndOfTurn, 1, false, true); + super(Duration.EndOfTurn, 1, true); staticText = "The next 1 damage that would be dealt to enchanted creature this turn is dealt to target creature or player instead"; } @@ -106,35 +106,21 @@ class WardOfPietyPreventDamageTargetEffect extends PreventionEffectImpl { } @Override - public boolean replaceEvent(GameEvent event, Ability source, Game game) { - PreventionEffectData preventionData = preventDamageAction(event, source, game); - // deal damage now - if (preventionData.getPreventedDamage() > 0) { - Permanent permanent = game.getPermanent(getTargetPointer().getFirst(game, source)); - if (permanent != null) { - game.informPlayers("Dealing " + preventionData.getPreventedDamage() + " damage to " + permanent.getLogName() + " instead"); - // keep the original source id as it is redirecting - permanent.damage(preventionData.getPreventedDamage(), event.getSourceId(), game, false, true); - } - Player player = game.getPlayer(getTargetPointer().getFirst(game, source)); - if (player != null) { - game.informPlayers("Dealing " + preventionData.getPreventedDamage() + " damage to " + player.getLogName() + " instead"); - // keep the original source id as it is redirecting - player.damage(preventionData.getPreventedDamage(), event.getSourceId(), game, false, true); - } - } - return false; + public void init(Ability source, Game game) { + super.init(source, game); + redirectToObject = new MageObjectReference(source.getTargets().get(0).getFirstTarget(), game); } @Override public boolean applies(GameEvent event, Ability source, Game game) { - if (super.applies(event, source, game)) { - Permanent enchantment = game.getPermanent(source.getSourceId()); - if (enchantment != null && event.getTargetId().equals(enchantment.getAttachedTo())) { + Permanent enchantment = game.getPermanent(source.getSourceId()); + if (enchantment != null && event.getTargetId().equals(enchantment.getAttachedTo())) { + if (redirectToObject.equals(new MageObjectReference(source.getTargets().get(0).getFirstTarget(), game))) { + redirectTarget = source.getTargets().get(0); return true; } } return false; } -} \ No newline at end of file +} diff --git a/Mage.Sets/src/mage/sets/championsofkamigawa/VassalsDuty.java b/Mage.Sets/src/mage/sets/championsofkamigawa/VassalsDuty.java index 71230bc44e6..abd2d87510e 100644 --- a/Mage.Sets/src/mage/sets/championsofkamigawa/VassalsDuty.java +++ b/Mage.Sets/src/mage/sets/championsofkamigawa/VassalsDuty.java @@ -31,8 +31,7 @@ import java.util.UUID; import mage.abilities.Ability; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.costs.mana.GenericManaCost; -import mage.abilities.effects.PreventionEffectData; -import mage.abilities.effects.PreventionEffectImpl; +import mage.abilities.effects.RedirectionEffect; import mage.cards.CardImpl; import mage.constants.CardType; import mage.constants.Duration; @@ -42,8 +41,7 @@ import mage.filter.common.FilterControlledCreaturePermanent; import mage.filter.predicate.mageobject.SupertypePredicate; import mage.game.Game; import mage.game.events.GameEvent; -import mage.game.permanent.Permanent; -import mage.players.Player; +import mage.target.TargetPlayer; import mage.target.common.TargetControlledCreaturePermanent; /** @@ -53,6 +51,7 @@ import mage.target.common.TargetControlledCreaturePermanent; public class VassalsDuty extends CardImpl { private static final FilterControlledCreaturePermanent filter = new FilterControlledCreaturePermanent("legendary creature you control"); + static { filter.add(new SupertypePredicate("Legendary")); } @@ -61,10 +60,9 @@ public class VassalsDuty extends CardImpl { super(ownerId, 48, "Vassal's Duty", Rarity.RARE, new CardType[]{CardType.ENCHANTMENT}, "{3}{W}"); this.expansionSetCode = "CHK"; - // {1}: The next 1 damage that would be dealt to target legendary creature you control this turn is dealt to you instead. Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new VassalsDutyPreventDamageTargetEffect(Duration.EndOfTurn, 1), new GenericManaCost(1)); - ability.addTarget(new TargetControlledCreaturePermanent(1,1,filter, false)); + ability.addTarget(new TargetControlledCreaturePermanent(1, 1, filter, false)); this.addAbility(ability); } @@ -78,10 +76,10 @@ public class VassalsDuty extends CardImpl { } } -class VassalsDutyPreventDamageTargetEffect extends PreventionEffectImpl { +class VassalsDutyPreventDamageTargetEffect extends RedirectionEffect { public VassalsDutyPreventDamageTargetEffect(Duration duration, int amount) { - super(duration, amount, false); + super(duration, amount, true); staticText = "The next " + amount + " damage that would be dealt to target legendary creature you control this turn is dealt to you instead"; } @@ -94,29 +92,13 @@ class VassalsDutyPreventDamageTargetEffect extends PreventionEffectImpl { return new VassalsDutyPreventDamageTargetEffect(this); } - @Override - public boolean replaceEvent(GameEvent event, Ability source, Game game) { - PreventionEffectData preventionResult = preventDamageAction(event, source, game); - // deal damage now - if (preventionResult.getPreventedDamage() > 0) { - UUID redirectTo = source.getControllerId(); - Player player = game.getPlayer(redirectTo); - if (player != null) { - game.informPlayers("Dealing " + preventionResult.getPreventedDamage() + " to " + player.getLogName() + " instead"); - // keep the original source id as it is redirecting - player.damage(preventionResult.getPreventedDamage(), event.getSourceId(), game, false, true); - } - } - // damage amount is reduced or set to 0 so complete replacement of damage event is never neccessary - return false; - } - @Override public boolean applies(GameEvent event, Ability source, Game game) { - if (!this.used && super.applies(event, source, game)) { - if (event.getTargetId().equals(getTargetPointer().getFirst(game, source))) { - return game.getPermanent(event.getTargetId()) != null; - } + if (event.getTargetId().equals(getTargetPointer().getFirst(game, source))) { + TargetPlayer target = new TargetPlayer(); + target.add(source.getControllerId(), game); + redirectTarget = target; + return true; } return false; } diff --git a/Mage.Sets/src/mage/sets/planechase/RaziaBorosArchangel.java b/Mage.Sets/src/mage/sets/planechase/RaziaBorosArchangel.java index 720d19460c3..be5502dbe76 100644 --- a/Mage.Sets/src/mage/sets/planechase/RaziaBorosArchangel.java +++ b/Mage.Sets/src/mage/sets/planechase/RaziaBorosArchangel.java @@ -29,11 +29,12 @@ package mage.sets.planechase; import java.util.UUID; import mage.MageInt; +import mage.MageObjectReference; import mage.abilities.Ability; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.costs.common.TapSourceCost; import mage.abilities.effects.Effect; -import mage.abilities.effects.PreventionEffectImpl; +import mage.abilities.effects.RedirectionEffect; import mage.abilities.keyword.FlyingAbility; import mage.abilities.keyword.HasteAbility; import mage.abilities.keyword.VigilanceAbility; @@ -42,9 +43,11 @@ import mage.constants.CardType; import mage.constants.Duration; import mage.constants.Rarity; import mage.constants.Zone; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.mageobject.AnotherTargetPredicate; import mage.game.Game; import mage.game.events.GameEvent; -import mage.game.permanent.Permanent; +import mage.target.Target; import mage.target.common.TargetControlledCreaturePermanent; import mage.target.common.TargetCreaturePermanent; @@ -72,11 +75,18 @@ public class RaziaBorosArchangel extends CardImpl { // Haste this.addAbility(HasteAbility.getInstance()); - // {tap}: The next 3 damage that would be dealt to target creature you control this turn is dealt to another target creature instead. + // {T}: The next 3 damage that would be dealt to target creature you control this turn is dealt to another target creature instead. Effect effect = new RaziaBorosArchangelEffect(Duration.EndOfTurn, 3); Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, effect, new TapSourceCost()); - ability.addTarget(new TargetControlledCreaturePermanent()); - ability.addTarget(new TargetCreaturePermanent()); + Target target = new TargetControlledCreaturePermanent(); + target.setTargetTag(1); + ability.addTarget(target); + + FilterCreaturePermanent filter = new FilterCreaturePermanent("creature (damage is redirected to)"); + filter.add(new AnotherTargetPredicate(2)); + target = new TargetCreaturePermanent(filter); + target.setTargetTag(2); + ability.addTarget(target); this.addAbility(ability); } @@ -91,19 +101,17 @@ public class RaziaBorosArchangel extends CardImpl { } } -class RaziaBorosArchangelEffect extends PreventionEffectImpl { +class RaziaBorosArchangelEffect extends RedirectionEffect { - private int amount; + protected MageObjectReference redirectToObject; public RaziaBorosArchangelEffect(Duration duration, int amount) { - super(duration); - this.amount = amount; + super(duration, 3, true); staticText = "The next " + amount + " damage that would be dealt to target creature you control this turn is dealt to another target creature instead"; } public RaziaBorosArchangelEffect(final RaziaBorosArchangelEffect effect) { super(effect); - this.amount = effect.amount; } @Override @@ -117,42 +125,16 @@ class RaziaBorosArchangelEffect extends PreventionEffectImpl { } @Override - public boolean replaceEvent(GameEvent event, Ability source, Game game) { - GameEvent preventEvent = new GameEvent(GameEvent.EventType.PREVENT_DAMAGE, source.getFirstTarget(), source.getSourceId(), source.getControllerId(), event.getAmount(), false); - if (!game.replaceEvent(preventEvent)) { - int prevented; - if (event.getAmount() >= this.amount) { - int damage = amount; - event.setAmount(event.getAmount() - amount); - this.used = true; - game.fireEvent(GameEvent.getEvent(GameEvent.EventType.PREVENTED_DAMAGE, source.getFirstTarget(), source.getSourceId(), source.getControllerId(), damage)); - prevented = damage; - } else { - int damage = event.getAmount(); - event.setAmount(0); - amount -= damage; - game.fireEvent(GameEvent.getEvent(GameEvent.EventType.PREVENTED_DAMAGE, source.getFirstTarget(), source.getSourceId(), source.getControllerId(), damage)); - prevented = damage; - } - - // deal damage now - if (prevented > 0) { - UUID redirectTo = source.getTargets().get(1).getFirstTarget(); - Permanent permanent = game.getPermanent(redirectTo); - if (permanent != null) { - game.informPlayers("Dealing " + prevented + " to " + permanent.getName() + " instead"); - // keep the original source id as it is redirecting - permanent.damage(prevented, event.getSourceId(), game, false, true); - } - } - } - return false; + public void init(Ability source, Game game) { + super.init(source, game); + redirectToObject = new MageObjectReference(source.getTargets().get(1).getFirstTarget(), game); } @Override public boolean applies(GameEvent event, Ability source, Game game) { - if (!this.used && super.applies(event, source, game)) { - if (source.getTargets().getFirstTarget().equals(event.getTargetId())) { + if (event.getTargetId().equals(getTargetPointer().getFirst(game, source))) { + if (redirectToObject.equals(new MageObjectReference(source.getTargets().get(1).getFirstTarget(), game))) { + redirectTarget = source.getTargets().get(1); return true; } } diff --git a/Mage.Sets/src/mage/sets/stronghold/NomadsEnKor.java b/Mage.Sets/src/mage/sets/stronghold/NomadsEnKor.java index c1dbfdd8b84..1c3d0aa8da8 100644 --- a/Mage.Sets/src/mage/sets/stronghold/NomadsEnKor.java +++ b/Mage.Sets/src/mage/sets/stronghold/NomadsEnKor.java @@ -32,8 +32,10 @@ import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.effects.common.RedirectDamageFromSourceToTargetEffect; import mage.cards.CardImpl; import mage.constants.CardType; +import mage.constants.Duration; import mage.constants.Rarity; import mage.constants.Zone; import mage.target.common.TargetControlledCreaturePermanent; @@ -55,7 +57,7 @@ public class NomadsEnKor extends CardImpl { this.toughness = new MageInt(1); // {0}: The next 1 damage that would be dealt to Nomads en-Kor this turn is dealt to target creature you control instead. - Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new ShamanEnKorRedirectFromItselfEffect(), new GenericManaCost(0)); + Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new RedirectDamageFromSourceToTargetEffect(Duration.EndOfTurn, 1, true), new GenericManaCost(0)); ability.addTarget(new TargetControlledCreaturePermanent()); this.addAbility(ability); } @@ -68,4 +70,4 @@ public class NomadsEnKor extends CardImpl { public NomadsEnKor copy() { return new NomadsEnKor(this); } -} \ No newline at end of file +} diff --git a/Mage.Sets/src/mage/sets/stronghold/ShamanEnKor.java b/Mage.Sets/src/mage/sets/stronghold/ShamanEnKor.java index 0bac73f71d5..e112c7f7821 100644 --- a/Mage.Sets/src/mage/sets/stronghold/ShamanEnKor.java +++ b/Mage.Sets/src/mage/sets/stronghold/ShamanEnKor.java @@ -35,6 +35,7 @@ import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.costs.mana.GenericManaCost; import mage.abilities.costs.mana.ManaCostsImpl; import mage.abilities.effects.RedirectionEffect; +import mage.abilities.effects.common.RedirectDamageFromSourceToTargetEffect; import mage.cards.CardImpl; import mage.constants.CardType; import mage.constants.Duration; @@ -67,7 +68,8 @@ public class ShamanEnKor extends CardImpl { this.toughness = new MageInt(2); // {0}: The next 1 damage that would be dealt to Shaman en-Kor this turn is dealt to target creature you control instead. - Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new ShamanEnKorRedirectFromItselfEffect(), new GenericManaCost(0)); + Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, + new RedirectDamageFromSourceToTargetEffect(Duration.EndOfTurn, 1, true), new GenericManaCost(0)); ability.addTarget(new TargetControlledCreaturePermanent()); this.addAbility(ability); @@ -87,32 +89,6 @@ public class ShamanEnKor extends CardImpl { } } -class ShamanEnKorRedirectFromItselfEffect extends RedirectionEffect { - - ShamanEnKorRedirectFromItselfEffect() { - super(Duration.EndOfTurn, 1, true); - staticText = "The next 1 damage that would be dealt to {this} this turn is dealt to target creature you control instead."; - } - - ShamanEnKorRedirectFromItselfEffect(final ShamanEnKorRedirectFromItselfEffect effect) { - super(effect); - } - - @Override - public ShamanEnKorRedirectFromItselfEffect copy() { - return new ShamanEnKorRedirectFromItselfEffect(this); - } - - @Override - public boolean applies(GameEvent event, Ability source, Game game) { - if (event.getTargetId().equals(source.getSourceId())) { - this.redirectTarget = source.getTargets().get(0); - return true; - } - return false; - } -} - class ShamanEnKorRedirectFromTargetEffect extends RedirectionEffect { protected MageObjectReference sourceObject; diff --git a/Mage.Sets/src/mage/sets/tempestremastered/SpiritEnKor.java b/Mage.Sets/src/mage/sets/tempestremastered/SpiritEnKor.java index e07c177ea74..4b4998f4949 100644 --- a/Mage.Sets/src/mage/sets/tempestremastered/SpiritEnKor.java +++ b/Mage.Sets/src/mage/sets/tempestremastered/SpiritEnKor.java @@ -32,18 +32,13 @@ import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.costs.mana.GenericManaCost; -import mage.abilities.effects.PreventionEffectData; -import mage.abilities.effects.PreventionEffectImpl; +import mage.abilities.effects.common.RedirectDamageFromSourceToTargetEffect; import mage.abilities.keyword.FlyingAbility; import mage.cards.CardImpl; import mage.constants.CardType; import mage.constants.Duration; import mage.constants.Rarity; import mage.constants.Zone; -import mage.game.Game; -import mage.game.events.DamageEvent; -import mage.game.events.GameEvent; -import mage.game.permanent.Permanent; import mage.target.common.TargetControlledCreaturePermanent; /** @@ -62,9 +57,10 @@ public class SpiritEnKor extends CardImpl { // Flying this.addAbility(FlyingAbility.getInstance()); - + // {0}: The next 1 damage that would be dealt to Spirit en-Kor this turn is dealt to target creature you control instead. - Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new SpiritEnKorPreventionEffect(), new GenericManaCost(0)); + Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, + new RedirectDamageFromSourceToTargetEffect(Duration.EndOfTurn, 1, true), new GenericManaCost(0)); ability.addTarget(new TargetControlledCreaturePermanent()); this.addAbility(ability); } @@ -78,44 +74,3 @@ public class SpiritEnKor extends CardImpl { return new SpiritEnKor(this); } } - -class SpiritEnKorPreventionEffect extends PreventionEffectImpl { - - SpiritEnKorPreventionEffect() { - super(Duration.EndOfTurn, 1, false); - staticText = "The next 1 damage that would be dealt to {this} this turn is dealt to target creature you control instead."; - } - - SpiritEnKorPreventionEffect(final SpiritEnKorPreventionEffect effect) { - super(effect); - } - - @Override - public SpiritEnKorPreventionEffect copy() { - return new SpiritEnKorPreventionEffect(this); - } - - @Override - public boolean replaceEvent(GameEvent event, Ability source, Game game) { - PreventionEffectData preventionResult = preventDamageAction(event, source, game); - if (preventionResult.getPreventedDamage() > 0) { - Permanent redirectTo = game.getPermanent(getTargetPointer().getFirst(game, source)); - if (redirectTo != null) { - game.informPlayers("Dealing " + preventionResult.getPreventedDamage() + " to " + redirectTo.getName() + " instead."); - DamageEvent damageEvent = (DamageEvent) event; - redirectTo.damage(preventionResult.getPreventedDamage(), event.getSourceId(), game, damageEvent.isCombatDamage(), damageEvent.isPreventable(), event.getAppliedEffects()); - } - } - return false; - } - - @Override - public boolean applies(GameEvent event, Ability source, Game game) { - if (!this.used && super.applies(event, source, game)) { - if (event.getTargetId().equals(source.getSourceId())) { - return game.getPermanent(getTargetPointer().getFirst(game, source)) != null; - } - } - return false; - } -} \ No newline at end of file diff --git a/Mage.Sets/src/mage/sets/tempestremastered/WarriorEnKor.java b/Mage.Sets/src/mage/sets/tempestremastered/WarriorEnKor.java index 4d991b1b1f0..4587dbd24e6 100644 --- a/Mage.Sets/src/mage/sets/tempestremastered/WarriorEnKor.java +++ b/Mage.Sets/src/mage/sets/tempestremastered/WarriorEnKor.java @@ -32,17 +32,12 @@ import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.costs.mana.GenericManaCost; -import mage.abilities.effects.PreventionEffectData; -import mage.abilities.effects.PreventionEffectImpl; +import mage.abilities.effects.common.RedirectDamageFromSourceToTargetEffect; import mage.cards.CardImpl; import mage.constants.CardType; import mage.constants.Duration; import mage.constants.Rarity; import mage.constants.Zone; -import mage.game.Game; -import mage.game.events.DamageEvent; -import mage.game.events.GameEvent; -import mage.game.permanent.Permanent; import mage.target.common.TargetControlledCreaturePermanent; /** @@ -61,7 +56,8 @@ public class WarriorEnKor extends CardImpl { this.toughness = new MageInt(2); // {0}: The next 1 damage that would be dealt to Warrior en-Kor this turn is dealt to target creature you control instead. - Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new WarriorEnKorPreventionEffect(), new GenericManaCost(0)); + Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, + new RedirectDamageFromSourceToTargetEffect(Duration.EndOfTurn, 1, true), new GenericManaCost(0)); ability.addTarget(new TargetControlledCreaturePermanent()); this.addAbility(ability); } @@ -75,44 +71,3 @@ public class WarriorEnKor extends CardImpl { return new WarriorEnKor(this); } } - -class WarriorEnKorPreventionEffect extends PreventionEffectImpl { - - WarriorEnKorPreventionEffect() { - super(Duration.EndOfTurn, 1, false); - staticText = "The next 1 damage that would be dealt to {this} this turn is dealt to target creature you control instead."; - } - - WarriorEnKorPreventionEffect(final WarriorEnKorPreventionEffect effect) { - super(effect); - } - - @Override - public WarriorEnKorPreventionEffect copy() { - return new WarriorEnKorPreventionEffect(this); - } - - @Override - public boolean replaceEvent(GameEvent event, Ability source, Game game) { - PreventionEffectData preventionResult = preventDamageAction(event, source, game); - if (preventionResult.getPreventedDamage() > 0) { - Permanent redirectTo = game.getPermanent(getTargetPointer().getFirst(game, source)); - if (redirectTo != null) { - game.informPlayers("Dealing " + preventionResult.getPreventedDamage() + " to " + redirectTo.getName() + " instead."); - DamageEvent damageEvent = (DamageEvent) event; - redirectTo.damage(preventionResult.getPreventedDamage(), event.getSourceId(), game, damageEvent.isCombatDamage(), damageEvent.isPreventable(), event.getAppliedEffects()); - } - } - return false; - } - - @Override - public boolean applies(GameEvent event, Ability source, Game game) { - if (!this.used && super.applies(event, source, game)) { - if (event.getTargetId().equals(source.getSourceId())) { - return game.getPermanent(getTargetPointer().getFirst(game, source)) != null; - } - } - return false; - } -} \ No newline at end of file diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/replacement/redirect/WardOfPietyTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/replacement/redirect/WardOfPietyTest.java new file mode 100644 index 00000000000..17b12464d7f --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/replacement/redirect/WardOfPietyTest.java @@ -0,0 +1,71 @@ +/* + * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ +package org.mage.test.cards.replacement.redirect; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * + * @author LevelX2 + */ +public class WardOfPietyTest extends CardTestPlayerBase { + + @Test + public void testNonCombatDamageToPlayer() { + addCard(Zone.HAND, playerB, "Lightning Bolt"); + addCard(Zone.BATTLEFIELD, playerB, "Mountain"); + + // Enchant creature + // {1}{W}: The next 1 damage that would be dealt to enchanted creature this turn is dealt to target creature or player instead. + addCard(Zone.HAND, playerA, "Ward of Piety"); // {1}{W} + addCard(Zone.BATTLEFIELD, playerA, "Silvercoat Lion"); // 2/2 + addCard(Zone.BATTLEFIELD, playerA, "Plains", 6); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Ward of Piety", "Silvercoat Lion"); + + activateAbility(1, PhaseStep.BEGIN_COMBAT, playerA, "{1}{W}: The next 1 damage", playerB); + activateAbility(1, PhaseStep.BEGIN_COMBAT, playerA, "{1}{W}: The next 1 damage", playerB); + + castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerB, "Lightning Bolt", "Silvercoat Lion"); + + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertGraveyardCount(playerB, "Lightning Bolt", 1); + assertPermanentCount(playerA, "Ward of Piety", 1); + assertPermanentCount(playerA, "Silvercoat Lion", 1); + + assertLife(playerA, 20); + assertLife(playerB, 18); + + } + +} diff --git a/Mage/src/mage/abilities/effects/common/RedirectDamageFromSourceToTargetEffect.java b/Mage/src/mage/abilities/effects/common/RedirectDamageFromSourceToTargetEffect.java new file mode 100644 index 00000000000..61b35d63abd --- /dev/null +++ b/Mage/src/mage/abilities/effects/common/RedirectDamageFromSourceToTargetEffect.java @@ -0,0 +1,42 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package mage.abilities.effects.common; + +import mage.abilities.Ability; +import mage.abilities.effects.RedirectionEffect; +import mage.constants.Duration; +import mage.game.Game; +import mage.game.events.GameEvent; + +/** + * + * @author LevelX2 + */ +public class RedirectDamageFromSourceToTargetEffect extends RedirectionEffect { + + public RedirectDamageFromSourceToTargetEffect(Duration duration, int amountToRedirect, boolean oneUsage) { + super(duration, amountToRedirect, oneUsage); + staticText = "The next " + amountToRedirect + " damage that would be dealt to {this} this turn is dealt to target creature you control instead."; + } + + public RedirectDamageFromSourceToTargetEffect(final RedirectDamageFromSourceToTargetEffect effect) { + super(effect); + } + + @Override + public RedirectDamageFromSourceToTargetEffect copy() { + return new RedirectDamageFromSourceToTargetEffect(this); + } + + @Override + public boolean applies(GameEvent event, Ability source, Game game) { + if (event.getTargetId().equals(source.getSourceId())) { + this.redirectTarget = source.getTargets().get(0); + return true; + } + return false; + } +} From 32429b4a4c4f0d8fbc3601f03bebe7dd1da10c92 Mon Sep 17 00:00:00 2001 From: LevelX2 Date: Tue, 20 Oct 2015 23:26:35 +0200 Subject: [PATCH 12/25] * Lightning Storm - Fixed that the always only the original controller was asked to change the target. --- Mage.Sets/src/mage/sets/coldsnap/LightningStorm.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Mage.Sets/src/mage/sets/coldsnap/LightningStorm.java b/Mage.Sets/src/mage/sets/coldsnap/LightningStorm.java index fd05403b5b5..4505fa176ca 100644 --- a/Mage.Sets/src/mage/sets/coldsnap/LightningStorm.java +++ b/Mage.Sets/src/mage/sets/coldsnap/LightningStorm.java @@ -29,6 +29,7 @@ package mage.sets.coldsnap; import java.util.UUID; import mage.abilities.Ability; +import mage.abilities.ActivatedAbilityImpl; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.costs.common.DiscardTargetCost; import mage.abilities.dynamicvalue.DynamicValue; @@ -59,7 +60,6 @@ public class LightningStorm extends CardImpl { super(ownerId, 89, "Lightning Storm", Rarity.UNCOMMON, new CardType[]{CardType.INSTANT}, "{1}{R}{R}"); this.expansionSetCode = "CSP"; - // Lightning Storm deals X damage to target creature or player, where X is 3 plus the number of charge counters on it. Effect effect = new DamageTargetEffect(new LightningStormCountCondition(CounterType.CHARGE)); effect.setText("{this} deals X damage to target creature or player, where X is 3 plus the number of charge counters on it"); @@ -67,7 +67,7 @@ public class LightningStorm extends CardImpl { this.getSpellAbility().addTarget(new TargetCreatureOrPlayer()); // Discard a land card: Put two charge counters on Lightning Storm. You may choose a new target for it. Any player may activate this ability but only if Lightning Storm is on the stack. SimpleActivatedAbility ability = new SimpleActivatedAbility(Zone.STACK, - new LightningStormAddCounterEffect() , + new LightningStormAddCounterEffect(), new DiscardTargetCost(new TargetCardInHand(new FilterLandCard()))); ability.setMayActivate(TargetController.ANY); ability.addEffect(new InfoEffect("Any player may activate this ability but only if {this} is on the stack")); @@ -85,6 +85,7 @@ public class LightningStorm extends CardImpl { } class LightningStormCountCondition implements DynamicValue { + private final CounterType counter; public LightningStormCountCondition(CounterType counter) { @@ -141,7 +142,7 @@ class LightningStormAddCounterEffect extends OneShotEffect { Spell spell = game.getStack().getSpell(source.getSourceId()); if (spell != null) { spell.addCounters(CounterType.CHARGE.createInstance(2), game); - return spell.chooseNewTargets(game, source.getControllerId(), false, false, null); + return spell.chooseNewTargets(game, ((ActivatedAbilityImpl) source).getActivatorId(), false, false, null); } return false; } From 1b71f505064b82893003207fc29954de533fbed5 Mon Sep 17 00:00:00 2001 From: LevelX2 Date: Tue, 20 Oct 2015 23:48:51 +0200 Subject: [PATCH 13/25] * Pattern of Rebirth - Fixed that the player that may search was not always the controller of the enchanted creature. --- .../sets/urzasdestiny/PatternOfRebirth.java | 8 ++++---- .../common/DiesAttachedTriggeredAbility.java | 19 +++++++++++++++++++ Mage/src/mage/constants/SetTargetPointer.java | 4 ++-- 3 files changed, 25 insertions(+), 6 deletions(-) diff --git a/Mage.Sets/src/mage/sets/urzasdestiny/PatternOfRebirth.java b/Mage.Sets/src/mage/sets/urzasdestiny/PatternOfRebirth.java index 017063b773e..038d5fda7dc 100644 --- a/Mage.Sets/src/mage/sets/urzasdestiny/PatternOfRebirth.java +++ b/Mage.Sets/src/mage/sets/urzasdestiny/PatternOfRebirth.java @@ -32,12 +32,13 @@ import mage.abilities.Ability; import mage.abilities.common.DiesAttachedTriggeredAbility; import mage.abilities.effects.Effect; import mage.abilities.effects.common.AttachEffect; -import mage.abilities.effects.common.search.SearchLibraryPutInPlayEffect; +import mage.abilities.effects.common.search.SearchLibraryPutInPlayTargetPlayerEffect; import mage.abilities.keyword.EnchantAbility; import mage.cards.CardImpl; import mage.constants.CardType; import mage.constants.Outcome; import mage.constants.Rarity; +import mage.constants.SetTargetPointer; import mage.filter.common.FilterCreatureCard; import mage.target.TargetPermanent; import mage.target.common.TargetCardInLibrary; @@ -54,7 +55,6 @@ public class PatternOfRebirth extends CardImpl { this.expansionSetCode = "UDS"; this.subtype.add("Aura"); - // Enchant creature TargetPermanent auraTarget = new TargetCreaturePermanent(); this.getSpellAbility().addTarget(auraTarget); @@ -63,9 +63,9 @@ public class PatternOfRebirth extends CardImpl { this.addAbility(ability); // When enchanted creature dies, that creature's controller may search his or her library for a creature card and put that card onto the battlefield. If that player does, he or she shuffles his or her library. - Effect effect = new SearchLibraryPutInPlayEffect(new TargetCardInLibrary(new FilterCreatureCard()), false, true, Outcome.PutCreatureInPlay); + Effect effect = new SearchLibraryPutInPlayTargetPlayerEffect(new TargetCardInLibrary(new FilterCreatureCard()), false, true, Outcome.PutCreatureInPlay); effect.setText("that creature's controller may search his or her library for a creature card and put that card onto the battlefield. If that player does, he or she shuffles his or her library"); - this.addAbility(new DiesAttachedTriggeredAbility(effect, "enchanted creature", true, true)); + this.addAbility(new DiesAttachedTriggeredAbility(effect, "enchanted creature", true, true, SetTargetPointer.ATTACHED_TO_CONTROLLER)); } diff --git a/Mage/src/mage/abilities/common/DiesAttachedTriggeredAbility.java b/Mage/src/mage/abilities/common/DiesAttachedTriggeredAbility.java index 963df6092bc..11d4a2764f3 100644 --- a/Mage/src/mage/abilities/common/DiesAttachedTriggeredAbility.java +++ b/Mage/src/mage/abilities/common/DiesAttachedTriggeredAbility.java @@ -2,11 +2,13 @@ package mage.abilities.common; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.effects.Effect; +import mage.constants.SetTargetPointer; import mage.constants.Zone; import mage.game.Game; import mage.game.events.GameEvent; import mage.game.events.ZoneChangeEvent; import mage.game.permanent.Permanent; +import mage.target.targetpointer.FixedTarget; /** * "When enchanted/equipped creature dies" triggered ability @@ -17,6 +19,7 @@ public class DiesAttachedTriggeredAbility extends TriggeredAbilityImpl { private String attachedDescription; private boolean diesRuleText; + protected SetTargetPointer setTargetPointer; public DiesAttachedTriggeredAbility(Effect effect, String attachedDescription) { this(effect, attachedDescription, false); @@ -27,15 +30,21 @@ public class DiesAttachedTriggeredAbility extends TriggeredAbilityImpl { } public DiesAttachedTriggeredAbility(Effect effect, String attachedDescription, boolean optional, boolean diesRuleText) { + this(effect, attachedDescription, optional, diesRuleText, SetTargetPointer.NONE); + } + + public DiesAttachedTriggeredAbility(Effect effect, String attachedDescription, boolean optional, boolean diesRuleText, SetTargetPointer setTargetPointer) { super(Zone.ALL, effect, optional); // because the trigger only triggers if the object was attached, it doesn't matter where the Attachment was moved to (e.g. by replacement effect) after the trigger triggered, so Zone.all this.attachedDescription = attachedDescription; this.diesRuleText = diesRuleText; + this.setTargetPointer = setTargetPointer; } public DiesAttachedTriggeredAbility(final DiesAttachedTriggeredAbility ability) { super(ability); this.attachedDescription = ability.attachedDescription; this.diesRuleText = ability.diesRuleText; + this.setTargetPointer = ability.setTargetPointer; } @Override @@ -69,6 +78,16 @@ public class DiesAttachedTriggeredAbility extends TriggeredAbilityImpl { if (triggered) { for (Effect effect : getEffects()) { effect.setValue("attachedTo", zEvent.getTarget()); + if (setTargetPointer.equals(SetTargetPointer.ATTACHED_TO_CONTROLLER)) { + Permanent attachment = game.getPermanentOrLKIBattlefield(getSourceId()); + if (attachment != null && attachment.getAttachedTo() != null) { + Permanent attachedTo = (Permanent) game.getLastKnownInformation(attachment.getAttachedTo(), Zone.BATTLEFIELD, attachment.getAttachedToZoneChangeCounter()); + if (attachedTo != null) { + effect.setTargetPointer(new FixedTarget(attachedTo.getControllerId())); + } + } + + } } return true; } diff --git a/Mage/src/mage/constants/SetTargetPointer.java b/Mage/src/mage/constants/SetTargetPointer.java index 6aeca5e026d..9f4acc8876d 100644 --- a/Mage/src/mage/constants/SetTargetPointer.java +++ b/Mage/src/mage/constants/SetTargetPointer.java @@ -25,7 +25,6 @@ * authors and should not be interpreted as representing official policies, either expressed * or implied, of BetaSteward_at_googlemail.com. */ - package mage.constants; /** @@ -33,5 +32,6 @@ package mage.constants; * @author LevelX2 */ public enum SetTargetPointer { - NONE, PLAYER, SPELL, CARD, PERMANENT; + + NONE, PLAYER, SPELL, CARD, PERMANENT, ATTACHED_TO_CONTROLLER; } From 2b68d3e0a931e4420ca58066446735524b0fb8cb Mon Sep 17 00:00:00 2001 From: LevelX2 Date: Wed, 21 Oct 2015 08:03:05 +0200 Subject: [PATCH 14/25] xmage 1.4.4v9 --- Utils/release/getting_implemented_cards.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Utils/release/getting_implemented_cards.txt b/Utils/release/getting_implemented_cards.txt index f922aab6fba..bd7cacbd8a5 100644 --- a/Utils/release/getting_implemented_cards.txt +++ b/Utils/release/getting_implemented_cards.txt @@ -36,6 +36,9 @@ git log 7650f53dee0b4d480d2a63befed72b6c8197e752..head --diff-filter=A --name-st since 1.4.4.v8 git log 8c7dc7b2da3630b6dfec1390854fa2be11631c79..head --diff-filter=A --name-status | sed -ne "s/^A[^u]Mage.Sets\/src\/mage\/sets\///p" | sort > added_cards.txt +since 1.4.4.v9 +git log 1b71f505064b82893003207fc29954de533fbed5..head --diff-filter=A --name-status | sed -ne "s/^A[^u]Mage.Sets\/src\/mage\/sets\///p" | sort > added_cards.txt + 3. Copy added_cards.txt to trunk\Utils folder 4. Run script: > perl extract_in_wiki_format.perl From d20bbcfe0b616b987eccbb4aeb83355d80608f9e Mon Sep 17 00:00:00 2001 From: LevelX2 Date: Wed, 21 Oct 2015 15:14:20 +0200 Subject: [PATCH 15/25] * Fixed that permanents brought onto battlefield by search abilities were always tapped. --- .../enters/SearchEntersBattlefieldTest.java | 59 +++++++++++++++++++ .../search/SearchLibraryPutInPlayEffect.java | 2 +- 2 files changed, 60 insertions(+), 1 deletion(-) create mode 100644 Mage.Tests/src/test/java/org/mage/test/cards/abilities/enters/SearchEntersBattlefieldTest.java diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/enters/SearchEntersBattlefieldTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/enters/SearchEntersBattlefieldTest.java new file mode 100644 index 00000000000..b097f3a7eef --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/enters/SearchEntersBattlefieldTest.java @@ -0,0 +1,59 @@ +/* + * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ +package org.mage.test.cards.abilities.enters; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * + * @author LevelX2 + */ +public class SearchEntersBattlefieldTest extends CardTestPlayerBase { + + @Test + public void testLandAfterFetchUntapped() { + addCard(Zone.HAND, playerA, "Verdant Catacombs"); + addCard(Zone.LIBRARY, playerA, "Forest"); + + playLand(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Verdant Catacombs"); + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}, Pay"); + setChoice(playerA, "Forest"); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertGraveyardCount(playerA, "Verdant Catacombs", 1); + assertPermanentCount(playerA, "Forest", 1); + assertTapped("Forest", false); + + } + +} diff --git a/Mage/src/mage/abilities/effects/common/search/SearchLibraryPutInPlayEffect.java b/Mage/src/mage/abilities/effects/common/search/SearchLibraryPutInPlayEffect.java index c92cbb7540e..de9c442f289 100644 --- a/Mage/src/mage/abilities/effects/common/search/SearchLibraryPutInPlayEffect.java +++ b/Mage/src/mage/abilities/effects/common/search/SearchLibraryPutInPlayEffect.java @@ -90,7 +90,7 @@ public class SearchLibraryPutInPlayEffect extends SearchEffect { if (player.searchLibrary(target, game)) { if (target.getTargets().size() > 0) { player.moveCards(new CardsImpl(target.getTargets()).getCards(game), - Zone.BATTLEFIELD, source, game, true, false, false, null); + Zone.BATTLEFIELD, source, game, tapped, false, false, null); } player.shuffleLibrary(game); return true; From d906fc8c006219ba30caee47b3fe9e342174fdb9 Mon Sep 17 00:00:00 2001 From: LevelX2 Date: Wed, 21 Oct 2015 15:35:58 +0200 Subject: [PATCH 16/25] * Fixed that damage redirection to planeswalker did cause an exception. --- .../RedirectDamageToPlaneswalkerTest.java | 70 +++++++++++++++++++ .../abilities/effects/RedirectionEffect.java | 7 +- 2 files changed, 73 insertions(+), 4 deletions(-) create mode 100644 Mage.Tests/src/test/java/org/mage/test/cards/planeswalker/RedirectDamageToPlaneswalkerTest.java diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/planeswalker/RedirectDamageToPlaneswalkerTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/planeswalker/RedirectDamageToPlaneswalkerTest.java new file mode 100644 index 00000000000..3b95c44288e --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/planeswalker/RedirectDamageToPlaneswalkerTest.java @@ -0,0 +1,70 @@ +/* + * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ +package org.mage.test.cards.planeswalker; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import mage.counters.CounterType; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * + * @author LevelX2 + */ +public class RedirectDamageToPlaneswalkerTest extends CardTestPlayerBase { + + @Test + public void testDirectDamage() { + // +2: Look at the top card of target player's library. You may put that card on the bottom of that player's library. + // 0: Draw three cards, then put two cards from your hand on top of your library in any order. + // −1: Return target creature to its owner's hand. + addCard(Zone.BATTLEFIELD, playerA, "Jace, the Mind Sculptor"); // starts with 3 Loyality counters + + addCard(Zone.BATTLEFIELD, playerB, "Mountain", 1); + addCard(Zone.HAND, playerB, "Lightning Bolt"); + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "+2:", playerB); + + castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerB, "Lightning Bolt", playerA); + setChoice(playerB, "Yes"); + + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertPermanentCount(playerA, "Jace, the Mind Sculptor", 1); + assertCounterCount("Jace, the Mind Sculptor", CounterType.LOYALTY, 2); // 3 + 2 - 3 = 2 + + assertGraveyardCount(playerB, "Lightning Bolt", 1); + + assertLife(playerA, 20); + assertLife(playerB, 20); + + } + +} diff --git a/Mage/src/mage/abilities/effects/RedirectionEffect.java b/Mage/src/mage/abilities/effects/RedirectionEffect.java index b30400e656e..7979ad8f1b1 100644 --- a/Mage/src/mage/abilities/effects/RedirectionEffect.java +++ b/Mage/src/mage/abilities/effects/RedirectionEffect.java @@ -27,7 +27,6 @@ */ package mage.abilities.effects; -import mage.MageObject; import mage.abilities.Ability; import mage.constants.Duration; import mage.constants.EffectType; @@ -80,7 +79,7 @@ public abstract class RedirectionEffect extends ReplacementEffectImpl { @Override public boolean replaceEvent(GameEvent event, Ability source, Game game) { - MageObject sourceObject = game.getObject(source.getSourceId()); + String sourceLogName = source != null ? game.getObject(source.getSourceId()).getLogName() + ": " : ""; DamageEvent damageEvent = (DamageEvent) event; int restDamage = 0; int damageToRedirect = event.getAmount(); @@ -94,12 +93,12 @@ public abstract class RedirectionEffect extends ReplacementEffectImpl { Permanent permanent = game.getPermanent(redirectTarget.getFirstTarget()); if (permanent != null) { permanent.damage(damageToRedirect, event.getSourceId(), game, damageEvent.isCombatDamage(), damageEvent.isPreventable(), event.getAppliedEffects()); - game.informPlayers(sourceObject.getLogName() + ": Redirected " + damageToRedirect + " damage to " + permanent.getLogName()); + game.informPlayers(sourceLogName + "Redirected " + damageToRedirect + " damage to " + permanent.getLogName()); } else { Player player = game.getPlayer(redirectTarget.getFirstTarget()); if (player != null) { player.damage(damageToRedirect, event.getSourceId(), game, damageEvent.isCombatDamage(), damageEvent.isPreventable(), event.getAppliedEffects()); - game.informPlayers(sourceObject.getLogName() + ": Redirected " + damageToRedirect + " damage to " + player.getLogName()); + game.informPlayers(sourceLogName + "Redirected " + damageToRedirect + " damage to " + player.getLogName()); } } if (restDamage > 0) { From 601dd29c9e77c39352fcf13167d1c1eb3bee82ea Mon Sep 17 00:00:00 2001 From: LevelX2 Date: Wed, 21 Oct 2015 15:44:22 +0200 Subject: [PATCH 17/25] * Golgari Grave-Troll - Fixed that it always came to battlefield with 0 +1/+1 counters. --- Mage.Sets/src/mage/sets/ravnica/GolgariGraveTroll.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Mage.Sets/src/mage/sets/ravnica/GolgariGraveTroll.java b/Mage.Sets/src/mage/sets/ravnica/GolgariGraveTroll.java index 1673b718af2..d0c851b05b3 100644 --- a/Mage.Sets/src/mage/sets/ravnica/GolgariGraveTroll.java +++ b/Mage.Sets/src/mage/sets/ravnica/GolgariGraveTroll.java @@ -28,10 +28,6 @@ package mage.sets.ravnica; import java.util.UUID; -import mage.constants.CardType; -import mage.constants.Outcome; -import mage.constants.Rarity; -import mage.constants.Zone; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.EntersBattlefieldAbility; @@ -42,6 +38,10 @@ import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.RegenerateSourceEffect; import mage.abilities.keyword.DredgeAbility; import mage.cards.CardImpl; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.Rarity; +import mage.constants.Zone; import mage.counters.CounterType; import mage.filter.common.FilterCreatureCard; import mage.filter.predicate.mageobject.CardTypePredicate; @@ -104,7 +104,7 @@ class GolgariGraveTrollEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { Player player = game.getPlayer(source.getControllerId()); - Permanent permanent = game.getPermanent(source.getSourceId()); + Permanent permanent = game.getPermanentEntering(source.getSourceId()); if (permanent != null && player != null) { int amount = player.getGraveyard().count(filter, game); if (amount > 0) { From 0f3a72de06f4dabf1bad1f9417647cc1480fd4c6 Mon Sep 17 00:00:00 2001 From: LevelX2 Date: Wed, 21 Oct 2015 15:45:21 +0200 Subject: [PATCH 18/25] * Ulasht, the Hate Seed - Fixed that it always came to battlefield with zero +1/+1 counters. --- Mage.Sets/src/mage/sets/guildpact/UlashtTheHateSeed.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Mage.Sets/src/mage/sets/guildpact/UlashtTheHateSeed.java b/Mage.Sets/src/mage/sets/guildpact/UlashtTheHateSeed.java index 6d99f312a7e..7903fdc13dc 100644 --- a/Mage.Sets/src/mage/sets/guildpact/UlashtTheHateSeed.java +++ b/Mage.Sets/src/mage/sets/guildpact/UlashtTheHateSeed.java @@ -73,7 +73,7 @@ public class UlashtTheHateSeed extends CardImpl { // Ulasht, the Hate Seed enters the battlefield with a +1/+1 counter on it for each other red creature you control and a +1/+1 counter on it for each other green creature you control. this.addAbility(new EntersBattlefieldAbility(new UlashtTheHateSeedEffect(), "with a +1/+1 counter on it for each other red creature you control and a +1/+1 counter on it for each other green creature you control.")); - + // {1}, Remove a +1/+1 counter from Ulasht: Choose one - Ulasht deals 1 damage to target creature; Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new DamageTargetEffect(1), new GenericManaCost(1)); ability.addCost(new RemoveCountersSourceCost(CounterType.P1P1.createInstance())); @@ -101,7 +101,7 @@ class UlashtTheHateSeedEffect extends OneShotEffect { private static final FilterControlledCreaturePermanent filterGreen = new FilterControlledCreaturePermanent(); private static final FilterControlledCreaturePermanent filterRed = new FilterControlledCreaturePermanent(); - + static { filterGreen.add(new AnotherPredicate()); filterGreen.add(new ColorPredicate(ObjectColor.GREEN)); @@ -121,7 +121,7 @@ class UlashtTheHateSeedEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { Player player = game.getPlayer(source.getControllerId()); - Permanent permanent = game.getPermanent(source.getSourceId()); + Permanent permanent = game.getPermanentEntering(source.getSourceId()); if (permanent != null && player != null) { int amount = game.getBattlefield().count(filterRed, source.getSourceId(), source.getControllerId(), game); amount += game.getBattlefield().count(filterGreen, source.getSourceId(), source.getControllerId(), game); @@ -138,4 +138,4 @@ class UlashtTheHateSeedEffect extends OneShotEffect { return new UlashtTheHateSeedEffect(this); } -} \ No newline at end of file +} From 353ddd9dfe83f849e2d65adeac27727b6bf360fd Mon Sep 17 00:00:00 2001 From: LevelX2 Date: Wed, 21 Oct 2015 15:45:43 +0200 Subject: [PATCH 19/25] * Unbreathing Horde - Fixed that it always came to battlefield with zero +1/+1 counters. --- Mage.Sets/src/mage/sets/innistrad/UnbreathingHorde.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Mage.Sets/src/mage/sets/innistrad/UnbreathingHorde.java b/Mage.Sets/src/mage/sets/innistrad/UnbreathingHorde.java index c51d4a005b9..75a7eed6473 100644 --- a/Mage.Sets/src/mage/sets/innistrad/UnbreathingHorde.java +++ b/Mage.Sets/src/mage/sets/innistrad/UnbreathingHorde.java @@ -102,7 +102,7 @@ class UnbreathingHordeEffect1 extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { Player player = game.getPlayer(source.getControllerId()); - Permanent permanent = game.getPermanent(source.getSourceId()); + Permanent permanent = game.getPermanentEntering(source.getSourceId()); if (permanent != null && player != null) { int amount = game.getBattlefield().countAll(filter1, source.getControllerId(), game) - 1; amount += player.getGraveyard().count(filter2, game); From f7c354afd3b6f19c8ec332d555db39d2cd910d87 Mon Sep 17 00:00:00 2001 From: LevelX2 Date: Wed, 21 Oct 2015 17:02:51 +0200 Subject: [PATCH 20/25] * Living Lore - Fixed that it did not get the +1/+1 counters as it entered the battlefield. --- .../mage/sets/dragonsoftarkir/LivingLore.java | 29 ++++++++++--------- .../src/mage/sets/tenthedition/Clone.java | 5 ---- .../SetPowerToughnessSourceEffect.java | 19 +++++------- 3 files changed, 24 insertions(+), 29 deletions(-) diff --git a/Mage.Sets/src/mage/sets/dragonsoftarkir/LivingLore.java b/Mage.Sets/src/mage/sets/dragonsoftarkir/LivingLore.java index 627a332ce4b..354d93ddef3 100644 --- a/Mage.Sets/src/mage/sets/dragonsoftarkir/LivingLore.java +++ b/Mage.Sets/src/mage/sets/dragonsoftarkir/LivingLore.java @@ -71,7 +71,7 @@ public class LivingLore extends CardImpl { this.addAbility(new EntersBattlefieldAbility(new LivingLoreExileEffect(), "exile an instant or sorcery card from your graveyard")); // Living Lore's power and toughness are each equal to the exiled card's converted mana cost. - this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new LivingLoreSetPowerToughnessSourceEffect())); + this.addAbility(new SimpleStaticAbility(Zone.ALL, new LivingLoreSetPowerToughnessSourceEffect())); // Whenever Living Lore deals combat damage, you may sacrifice it. If you do, you may cast the exiled card without paying its mana cost. this.addAbility(new DealsCombatDamageTriggeredAbility(new LivingLoreSacrificeEffect(), true)); @@ -106,14 +106,14 @@ class LivingLoreExileEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { Player controller = game.getPlayer(source.getControllerId()); - MageObject sourceObject = source.getSourceObject(game); - if (sourceObject != null && controller != null){ + Permanent sourcePermanent = game.getPermanentEntering(source.getSourceId()); + if (sourcePermanent != null && controller != null) { TargetCardInYourGraveyard target = new TargetCardInYourGraveyard(new FilterInstantOrSorceryCard("instant or sorcery card from your graveyard")); if (controller.chooseTarget(outcome, target, source, game)) { - UUID exileId = CardUtil.getExileZoneId(game, source.getSourceId(), source.getSourceObjectZoneChangeCounter()); + UUID exileId = CardUtil.getExileZoneId(game, source.getSourceId(), game.getState().getZoneChangeCounter(source.getSourceId()) + 1); Card card = controller.getGraveyard().get(target.getFirstTarget(), game); if (card != null) { - controller.moveCardToExileWithInfo(card, exileId, sourceObject.getIdName(), source.getSourceId(), game, Zone.GRAVEYARD, true); + controller.moveCardsToExile(card, source, game, true, exileId, sourcePermanent.getIdName()); } } return true; @@ -126,7 +126,7 @@ class LivingLoreExileEffect extends OneShotEffect { class LivingLoreSetPowerToughnessSourceEffect extends ContinuousEffectImpl { public LivingLoreSetPowerToughnessSourceEffect() { - super(Duration.WhileOnBattlefield, Layer.PTChangingEffects_7, SubLayer.SetPT_7b, Outcome.BoostCreature); + super(Duration.Custom, Layer.PTChangingEffects_7, SubLayer.SetPT_7b, Outcome.BoostCreature); staticText = "{this}'s power and toughness are each equal to the exiled card's converted mana cost"; } @@ -141,20 +141,23 @@ class LivingLoreSetPowerToughnessSourceEffect extends ContinuousEffectImpl { @Override public boolean apply(Game game, Ability source) { - MageObject mageObject = source.getSourceObject(game); Permanent permanent = game.getPermanent(source.getSourceId()); - if (permanent != null && mageObject == null && new MageObjectReference(permanent, game).refersTo(mageObject, game)) { - discard(); - return false; + int zcc = game.getState().getZoneChangeCounter(source.getSourceId()); + if (permanent == null) { + permanent = game.getPermanentEntering(source.getSourceId()); + zcc++; } - UUID exileId = CardUtil.getExileZoneId(game, source.getSourceId(), source.getSourceObjectZoneChangeCounter()); + if (permanent == null) { + return true; + } + UUID exileId = CardUtil.getExileZoneId(game, source.getSourceId(), zcc); if (exileId != null) { ExileZone exileZone = game.getExile().getExileZone(exileId); if (exileZone == null) { return false; } Card exiledCard = null; - for (Card card :exileZone.getCards(game)) { + for (Card card : exileZone.getCards(game)) { exiledCard = card; break; } @@ -197,7 +200,7 @@ class LivingLoreSacrificeEffect extends OneShotEffect { ExileZone exileZone = game.getExile().getExileZone(exileId); Card exiledCard = null; if (exileZone != null) { - for (Card card :exileZone.getCards(game)) { + for (Card card : exileZone.getCards(game)) { exiledCard = card; break; } diff --git a/Mage.Sets/src/mage/sets/tenthedition/Clone.java b/Mage.Sets/src/mage/sets/tenthedition/Clone.java index 6869479699a..6fa99a23821 100644 --- a/Mage.Sets/src/mage/sets/tenthedition/Clone.java +++ b/Mage.Sets/src/mage/sets/tenthedition/Clone.java @@ -50,11 +50,6 @@ public class Clone extends CardImpl { this.toughness = new MageInt(0); // You may have Clone enter the battlefield as a copy of any creature on the battlefield. -// ; -// Ability ability = new SimpleStaticAbility(Zone.BATTLEFIELD, new EntersBattlefieldEffect( -// new CopyPermanentEffect(), -// "You may have {this} enter the battlefield as a copy of any creature on the battlefield", -// true)); this.addAbility(new EntersBattlefieldAbility(new CopyPermanentEffect(), true)); } diff --git a/Mage/src/mage/abilities/effects/common/continuous/SetPowerToughnessSourceEffect.java b/Mage/src/mage/abilities/effects/common/continuous/SetPowerToughnessSourceEffect.java index d523a5cbd06..1c2e3dbc87a 100644 --- a/Mage/src/mage/abilities/effects/common/continuous/SetPowerToughnessSourceEffect.java +++ b/Mage/src/mage/abilities/effects/common/continuous/SetPowerToughnessSourceEffect.java @@ -36,7 +36,6 @@ import mage.constants.Layer; import mage.constants.Outcome; import mage.constants.SubLayer; import mage.game.Game; -import mage.game.permanent.Permanent; /** * @@ -75,19 +74,17 @@ public class SetPowerToughnessSourceEffect extends ContinuousEffectImpl { @Override public boolean apply(Game game, Ability source) { - MageObject mageObject = game.getObject(source.getSourceId()); + MageObject mageObject = game.getPermanentEntering(source.getSourceId()); if (mageObject == null) { - game.getPermanentEntering(source.getSourceId()); + if (duration.equals(Duration.Custom) || isTemporary()) { + mageObject = game.getPermanent(source.getSourceId()); + } else { + mageObject = game.getObject(source.getSourceId()); + } } if (mageObject == null) { - if (duration.equals(Duration.Custom)) { - discard(); - } - return false; - } else if (isTemporary()) { // it's somehow w - if (!(mageObject instanceof Permanent)) { - return false; - } + discard(); + return true; } if (amount != null) { int value = amount.calculate(game, source, this); From c9cb53101d5c36a79e84680bcd925285a3adab2a Mon Sep 17 00:00:00 2001 From: LevelX2 Date: Wed, 21 Oct 2015 17:05:36 +0200 Subject: [PATCH 21/25] * Path to Exile - Fixed that the land was put unter control of the controller of Path to Exile to the battlefield. --- Mage.Sets/src/mage/sets/conflux/PathToExile.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Mage.Sets/src/mage/sets/conflux/PathToExile.java b/Mage.Sets/src/mage/sets/conflux/PathToExile.java index 63f6a0c889a..ed66cc9597d 100644 --- a/Mage.Sets/src/mage/sets/conflux/PathToExile.java +++ b/Mage.Sets/src/mage/sets/conflux/PathToExile.java @@ -98,7 +98,7 @@ class PathToExileEffect extends OneShotEffect { if (player.searchLibrary(target, game)) { Card card = player.getLibrary().getCard(target.getFirstTarget(), game); if (card != null) { - controller.moveCards(card, Zone.BATTLEFIELD, source, game, true, false, false, null); + player.moveCards(card, Zone.BATTLEFIELD, source, game, true, false, false, null); } } player.shuffleLibrary(game); From da47f41682e2568a213898d34fe3ab197b56c25e Mon Sep 17 00:00:00 2001 From: LevelX2 Date: Wed, 21 Oct 2015 17:11:35 +0200 Subject: [PATCH 22/25] * Tooth and Nail - Fixed that second mode did not put the selected creature cards to battlefield. --- Mage.Sets/src/mage/sets/modernmasters/ToothAndNail.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Mage.Sets/src/mage/sets/modernmasters/ToothAndNail.java b/Mage.Sets/src/mage/sets/modernmasters/ToothAndNail.java index 30762aeb3d7..478914ee117 100644 --- a/Mage.Sets/src/mage/sets/modernmasters/ToothAndNail.java +++ b/Mage.Sets/src/mage/sets/modernmasters/ToothAndNail.java @@ -102,7 +102,7 @@ class ToothAndNailPutCreatureOnBattlefieldEffect extends OneShotEffect { TargetCardInHand target = new TargetCardInHand(0, 2, new FilterCreatureCard("creature cards")); if (controller.choose(Outcome.PutCreatureInPlay, target, source.getSourceId(), game)) { - return controller.moveCards(new CardsImpl(getTargetPointer().getTargets(game, source)).getCards(game), + return controller.moveCards(new CardsImpl(target.getTargets()).getCards(game), Zone.BATTLEFIELD, source, game, true, false, false, null); } return false; From 511fb378388af8b6e23a102b567a14ee990e95d1 Mon Sep 17 00:00:00 2001 From: LevelX2 Date: Wed, 21 Oct 2015 19:08:00 +0200 Subject: [PATCH 23/25] * Utopia Sprawl - Fixed that the color choice was not working. --- .../mage/sets/dissension/UtopiaSprawl.java | 61 +++---------------- 1 file changed, 8 insertions(+), 53 deletions(-) diff --git a/Mage.Sets/src/mage/sets/dissension/UtopiaSprawl.java b/Mage.Sets/src/mage/sets/dissension/UtopiaSprawl.java index c92e93989b1..de6633ee6af 100644 --- a/Mage.Sets/src/mage/sets/dissension/UtopiaSprawl.java +++ b/Mage.Sets/src/mage/sets/dissension/UtopiaSprawl.java @@ -32,20 +32,18 @@ import mage.Mana; import mage.ObjectColor; import mage.abilities.Ability; import mage.abilities.common.AsEntersBattlefieldAbility; -import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.AttachEffect; +import mage.abilities.effects.common.ChooseColorEffect; import mage.abilities.effects.common.ManaEffect; import mage.abilities.keyword.EnchantAbility; import mage.abilities.mana.TriggeredManaAbility; import mage.cards.CardImpl; -import mage.choices.ChoiceColor; import mage.constants.CardType; import mage.constants.ColoredManaSymbol; import mage.constants.Outcome; import mage.constants.Rarity; import mage.constants.Zone; import mage.filter.common.FilterLandPermanent; -import mage.filter.predicate.mageobject.SubtypePredicate; import mage.game.Game; import mage.game.events.GameEvent; import mage.game.events.GameEvent.EventType; @@ -60,18 +58,13 @@ import mage.target.common.TargetLandPermanent; */ public class UtopiaSprawl extends CardImpl { - private static final FilterLandPermanent filter = new FilterLandPermanent("Forest"); + private static final FilterLandPermanent filter = new FilterLandPermanent("Forest", "Forest"); - static { - filter.add(new SubtypePredicate("Forest")); - } - public UtopiaSprawl(UUID ownerId) { super(ownerId, 99, "Utopia Sprawl", Rarity.COMMON, new CardType[]{CardType.ENCHANTMENT}, "{G}"); this.expansionSetCode = "DIS"; this.subtype.add("Aura"); - // Enchant Forest TargetPermanent auraTarget = new TargetLandPermanent(filter); this.getSpellAbility().addTarget(auraTarget); @@ -79,7 +72,7 @@ public class UtopiaSprawl extends CardImpl { Ability ability = new EnchantAbility(auraTarget.getTargetName()); this.addAbility(ability); // As Utopia Sprawl enters the battlefield, choose a color. - this.addAbility(new AsEntersBattlefieldAbility(new ChooseColorEffect())); + this.addAbility(new AsEntersBattlefieldAbility(new ChooseColorEffect(Outcome.Detriment))); // Whenever enchanted Forest is tapped for mana, its controller adds one mana of the chosen color to his or her mana pool. this.addAbility(new UtopiaSprawlTriggeredAbility()); } @@ -94,42 +87,8 @@ public class UtopiaSprawl extends CardImpl { } } -class ChooseColorEffect extends OneShotEffect { - - public ChooseColorEffect() { - super(Outcome.BoostCreature); - staticText = "choose a color"; - } - - public ChooseColorEffect(final ChooseColorEffect effect) { - super(effect); - } - - @Override - public boolean apply(Game game, Ability source) { - Player player = game.getPlayer(source.getControllerId()); - Permanent permanent = game.getPermanent(source.getSourceId()); - if (player != null && permanent != null) { - ChoiceColor colorChoice = new ChoiceColor(); - if (player.choose(Outcome.Neutral, colorChoice, game)) { - game.informPlayers(permanent.getName() + ": " + player.getLogName() + " has chosen " + colorChoice.getChoice()); - game.getState().setValue(permanent.getId() + "_color", colorChoice.getColor()); - permanent.addInfo("chosen color", "Chosen color: " + colorChoice.getColor().getDescription() + "", game); - } - } - return false; - } - - @Override - public ChooseColorEffect copy() { - return new ChooseColorEffect(this); - } - -} - class UtopiaSprawlTriggeredAbility extends TriggeredManaAbility { - public UtopiaSprawlTriggeredAbility() { super(Zone.BATTLEFIELD, new UtopiaSprawlEffect()); } @@ -146,10 +105,7 @@ class UtopiaSprawlTriggeredAbility extends TriggeredManaAbility { @Override public boolean checkTrigger(GameEvent event, Game game) { Permanent enchantment = game.getPermanent(this.getSourceId()); - if (enchantment != null && event.getSourceId().equals(enchantment.getAttachedTo())) { - return true; - } - return false; + return enchantment != null && event.getSourceId().equals(enchantment.getAttachedTo()); } @Override @@ -159,11 +115,10 @@ class UtopiaSprawlTriggeredAbility extends TriggeredManaAbility { @Override public String getRule() { - return "Whenever enchanted Forest is tapped for mana, its controller adds one mana of the chosen color to his or her mana pool"; + return "Whenever enchanted Forest is tapped for mana, its controller adds one mana of the chosen color to his or her mana pool."; } } - class UtopiaSprawlEffect extends ManaEffect { public UtopiaSprawlEffect() { @@ -178,9 +133,9 @@ class UtopiaSprawlEffect extends ManaEffect { @Override public boolean apply(Game game, Ability source) { Permanent enchantment = game.getPermanent(source.getSourceId()); - if(enchantment != null){ + if (enchantment != null) { Permanent land = game.getPermanent(enchantment.getAttachedTo()); - if(land != null){ + if (land != null) { Player player = game.getPlayer(land.getControllerId()); if (player != null) { player.getManaPool().addMana(getMana(game, source), game, source); @@ -205,4 +160,4 @@ class UtopiaSprawlEffect extends ManaEffect { public UtopiaSprawlEffect copy() { return new UtopiaSprawlEffect(this); } -} \ No newline at end of file +} From 4629366ae7a29edf12d428e45638a0273953fd3f Mon Sep 17 00:00:00 2001 From: LevelX2 Date: Wed, 21 Oct 2015 20:16:52 +0200 Subject: [PATCH 24/25] * Fixed that spells without mana costs but suspend could be cast with no mana (e.g. Ancestral Vision). --- .../cards/abilities/keywords/SuspendTest.java | 16 +++++++++++++++ Mage/src/mage/players/PlayerImpl.java | 20 ++++++++++++++++++- 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/SuspendTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/SuspendTest.java index 9ed98cca08f..8e66883abc3 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/SuspendTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/SuspendTest.java @@ -147,4 +147,20 @@ public class SuspendTest extends CardTestPlayerBase { assertCounterOnExiledCardCount("Deep-Sea Kraken", CounterType.TIME, 8); // -1 from spell of player B } + + @Test + public void testAncestralVisionCantBeCastDirectly() { + // Suspend 4-{U} + // Target player draws three cards. + addCard(Zone.HAND, playerA, "Ancestral Vision", 1); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Ancestral Vision", playerA); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertHandCount(playerA, 1); + assertHandCount(playerA, "Ancestral Vision", 1); + + } } diff --git a/Mage/src/mage/players/PlayerImpl.java b/Mage/src/mage/players/PlayerImpl.java index 1719603aa72..cdeb49c91a4 100644 --- a/Mage/src/mage/players/PlayerImpl.java +++ b/Mage/src/mage/players/PlayerImpl.java @@ -1217,11 +1217,15 @@ public abstract class PlayerImpl implements Player, Serializable { public LinkedHashMap getUseableActivatedAbilities(MageObject object, Zone zone, Game game) { LinkedHashMap useable = new LinkedHashMap<>(); boolean canUse = !(object instanceof Permanent) || ((Permanent) object).canUseActivatedAbilities(game); + ManaOptions availableMana = null; +// ManaOptions availableMana = getManaAvailable(game); // can only be activated if mana calculation works flawless otherwise player can't play spells they could play if calculation would work correctly +// availableMana.addMana(manaPool.getMana()); for (Ability ability : object.getAbilities()) { if (canUse || ability.getAbilityType().equals(AbilityType.SPECIAL_ACTION)) { if (ability.getZone().match(zone)) { if (ability instanceof ActivatedAbility) { - if (((ActivatedAbility) ability).canActivate(playerId, game)) { + if (canPlay(((ActivatedAbility) ability), availableMana, object, game)) { +// if (((ActivatedAbility) ability).canActivate(playerId, game)) { useable.put(ability.getId(), (ActivatedAbility) ability); } } else if (ability instanceof AlternativeSourceCosts) { @@ -2297,6 +2301,14 @@ public abstract class PlayerImpl implements Player, Serializable { return result; } + /** + * + * @param ability + * @param available if null, it won't be checked if enough mana is available + * @param sourceObject + * @param game + * @return + */ protected boolean canPlay(ActivatedAbility ability, ManaOptions available, MageObject sourceObject, Game game) { if (!(ability instanceof ManaAbility)) { ActivatedAbility copy = ability.copy(); @@ -2329,6 +2341,9 @@ public abstract class PlayerImpl implements Player, Serializable { if (abilityOptions.size() == 0) { return true; } else { + if (available == null) { + return true; + } for (Mana mana : abilityOptions) { for (Mana avail : available) { if (mana.enough(avail)) { @@ -2377,6 +2392,9 @@ public abstract class PlayerImpl implements Player, Serializable { if (manaCosts.size() == 0) { return true; } else { + if (available == null) { + return true; + } for (Mana mana : manaCosts.getOptions()) { for (Mana avail : available) { if (mana.enough(avail)) { From 305712806ccdaed941630df5f095f73de5b049ab Mon Sep 17 00:00:00 2001 From: LevelX2 Date: Wed, 21 Oct 2015 20:24:39 +0200 Subject: [PATCH 25/25] * Cursed Scroll - Fixed that the card name dialog was not opened. --- Mage.Sets/src/mage/sets/tempest/CursedScroll.java | 15 +++++++-------- .../abilities/effects/common/NameACardEffect.java | 2 +- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/Mage.Sets/src/mage/sets/tempest/CursedScroll.java b/Mage.Sets/src/mage/sets/tempest/CursedScroll.java index 110111ffe08..2f2c326a895 100644 --- a/Mage.Sets/src/mage/sets/tempest/CursedScroll.java +++ b/Mage.Sets/src/mage/sets/tempest/CursedScroll.java @@ -29,9 +29,6 @@ package mage.sets.tempest; import java.util.UUID; import mage.MageObject; - -import mage.constants.CardType; -import mage.constants.Rarity; import mage.abilities.Ability; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.costs.common.TapSourceCost; @@ -42,7 +39,9 @@ import mage.cards.Card; import mage.cards.CardImpl; import mage.cards.Cards; import mage.cards.CardsImpl; +import mage.constants.CardType; import mage.constants.Outcome; +import mage.constants.Rarity; import mage.constants.Zone; import mage.game.Game; import mage.game.permanent.Permanent; @@ -91,15 +90,15 @@ class CursedScrollEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Player you = game.getPlayer(source.getControllerId()); + Player controller = game.getPlayer(source.getControllerId()); MageObject sourceObject = game.getObject(source.getSourceId()); String cardName = (String) game.getState().getValue(source.getSourceId().toString() + NameACardEffect.INFO_KEY); - if (sourceObject != null && you != null && cardName != null && !cardName.isEmpty()) { - if (you.getHand().size() > 0) { + if (sourceObject != null && controller != null && cardName != null && !cardName.isEmpty()) { + if (controller.getHand().size() > 0) { Cards revealed = new CardsImpl(); - Card card = you.getHand().getRandom(game); + Card card = controller.getHand().getRandom(game); revealed.add(card); - you.revealCards(sourceObject.getName(), revealed, game); + controller.revealCards(sourceObject.getIdName(), revealed, game); if (card.getName().equals(cardName)) { Permanent creature = game.getPermanent(targetPointer.getFirst(game, source)); if (creature != null) { diff --git a/Mage/src/mage/abilities/effects/common/NameACardEffect.java b/Mage/src/mage/abilities/effects/common/NameACardEffect.java index 1a642fac0f0..7cf32682fa6 100644 --- a/Mage/src/mage/abilities/effects/common/NameACardEffect.java +++ b/Mage/src/mage/abilities/effects/common/NameACardEffect.java @@ -73,7 +73,7 @@ public class NameACardEffect extends OneShotEffect { Player controller = game.getPlayer(source.getControllerId()); MageObject sourceObject = game.getPermanentEntering(source.getSourceId()); if (sourceObject == null) { - game.getObject(source.getSourceId()); + sourceObject = game.getObject(source.getSourceId()); } if (controller != null && sourceObject != null) { Choice cardChoice = new ChoiceImpl();