From f9c48cc4d5b420da85b4d5351b4cc20d3664b9af Mon Sep 17 00:00:00 2001 From: LevelX2 Date: Thu, 30 Apr 2015 16:15:39 +0200 Subject: [PATCH] * Some updates to cards that check the controller of the source of an event. Now prevention and replacement effect controller of non permanents are determine. But it can be wrong under some control changing circumstances. Only solution would be to have always the causing ability instead of only the sourceId (fixes #804). --- .../sets/alarareborn/RetaliatorGriffin.java | 44 +++++----- .../sets/bornofthegods/SatyrFiredancer.java | 34 ++++---- .../championsofkamigawa/NightDealings.java | 41 ++++------ .../mage/sets/dragonsmaze/BlazeCommando.java | 30 +++++-- .../mage/sets/magic2010/GuardianSeraph.java | 43 +++------- .../src/mage/sets/mirrodin/FarsightMask.java | 50 ++++-------- .../sets/modernmasters/PyromancersSwath.java | 14 +--- .../MichikoKondaTruthSeeker.java | 26 +++--- .../mage/sets/zendikar/QuestForPureFlame.java | 40 +--------- .../oneshot/damage/DeflectingPalmTest.java | 80 +++++++++++++++++++ .../abilities/effects/ContinuousEffects.java | 31 +++++++ Mage/src/mage/game/Game.java | 1 + Mage/src/mage/game/GameImpl.java | 18 ++++- Mage/src/mage/game/events/GameEvent.java | 12 ++- 14 files changed, 262 insertions(+), 202 deletions(-) create mode 100644 Mage.Tests/src/test/java/org/mage/test/cards/abilities/oneshot/damage/DeflectingPalmTest.java diff --git a/Mage.Sets/src/mage/sets/alarareborn/RetaliatorGriffin.java b/Mage.Sets/src/mage/sets/alarareborn/RetaliatorGriffin.java index 50d62a45535..09b150888d8 100644 --- a/Mage.Sets/src/mage/sets/alarareborn/RetaliatorGriffin.java +++ b/Mage.Sets/src/mage/sets/alarareborn/RetaliatorGriffin.java @@ -36,13 +36,14 @@ import mage.MageInt; import mage.abilities.Ability; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.counter.AddCountersSourceEffect; import mage.abilities.keyword.FlyingAbility; import mage.cards.CardImpl; import mage.counters.CounterType; import mage.game.Game; import mage.game.events.GameEvent; import mage.game.permanent.Permanent; -import mage.game.stack.StackObject; +import mage.players.Player; /** * @@ -55,9 +56,6 @@ public class RetaliatorGriffin extends CardImpl { this.expansionSetCode = "ARB"; this.subtype.add("Griffin"); - - - this.power = new MageInt(2); this.toughness = new MageInt(2); @@ -79,7 +77,7 @@ public class RetaliatorGriffin extends CardImpl { class RetaliatorGriffinTriggeredAbility extends TriggeredAbilityImpl { public RetaliatorGriffinTriggeredAbility() { - super(Zone.BATTLEFIELD, new RetaliatorGriffinEffect(), false); + super(Zone.BATTLEFIELD, new RetaliatorGriffinEffect(), true); } public RetaliatorGriffinTriggeredAbility(final RetaliatorGriffinTriggeredAbility ability) { @@ -90,22 +88,19 @@ class RetaliatorGriffinTriggeredAbility extends TriggeredAbilityImpl { public RetaliatorGriffinTriggeredAbility copy() { return new RetaliatorGriffinTriggeredAbility(this); } - + + @Override + public boolean checkEventType(GameEvent event, Game game) { + return event.getType().equals(GameEvent.EventType.DAMAGED_PLAYER); + } + @Override public boolean checkTrigger(GameEvent event, Game game) { - if (event.getType().equals(GameEvent.EventType.DAMAGED_PLAYER)) { - UUID sourceControllerId = null; - Permanent permanent = game.getPermanent(event.getSourceId()); - if (permanent != null) { - sourceControllerId = permanent.getControllerId(); - } else { - StackObject sourceStackObject = game.getStack().getStackObject(event.getSourceId()); - if (sourceStackObject != null) { - sourceControllerId = sourceStackObject.getControllerId(); - } - } - if (event.getTargetId().equals(controllerId) && game.getOpponents(controllerId).contains(sourceControllerId)) { - getEffects().get(0).setValue("RetaliatorGriffinAmount", event.getAmount()); + if (event.getTargetId().equals(getControllerId())) { + UUID sourceControllerId = game.getControllerId(event.getSourceId()); + if (sourceControllerId != null && + game.getOpponents(getControllerId()).contains(sourceControllerId)) { + getEffects().get(0).setValue("damageAmount", event.getAmount()); return true; } } @@ -135,10 +130,13 @@ class RetaliatorGriffinEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Permanent permanent = game.getPermanent(source.getSourceId()); - Integer amount = (Integer) this.getValue("RetaliatorGriffinAmount"); - if (permanent != null && amount != null) { - permanent.addCounters(CounterType.P1P1.createInstance(amount), game); + Player controller = game.getPlayer(source.getControllerId()); + if (controller != null) { + Permanent permanent = game.getPermanent(source.getSourceId()); + Integer amount = (Integer) this.getValue("damageAmount"); + if (permanent != null && amount != null && amount > 0) { + new AddCountersSourceEffect(CounterType.P1P1.createInstance(amount), true).apply(game, source); + } return true; } return false; diff --git a/Mage.Sets/src/mage/sets/bornofthegods/SatyrFiredancer.java b/Mage.Sets/src/mage/sets/bornofthegods/SatyrFiredancer.java index 96671fc94a5..9f285c595fe 100644 --- a/Mage.Sets/src/mage/sets/bornofthegods/SatyrFiredancer.java +++ b/Mage.Sets/src/mage/sets/bornofthegods/SatyrFiredancer.java @@ -36,6 +36,7 @@ import mage.abilities.Ability; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.effects.Effect; import mage.abilities.effects.OneShotEffect; +import mage.cards.Card; import mage.cards.CardImpl; import mage.constants.CardType; import mage.constants.Outcome; @@ -63,7 +64,6 @@ public class SatyrFiredancer extends CardImpl { this.expansionSetCode = "BNG"; this.subtype.add("Satyr"); - this.color.setRed(true); this.power = new MageInt(1); this.toughness = new MageInt(1); @@ -95,7 +95,7 @@ public class SatyrFiredancer extends CardImpl { class SatyrFiredancerTriggeredAbility extends TriggeredAbilityImpl { - private List handledStackObjects = new ArrayList(); + private List handledStackObjects = new ArrayList<>(); public SatyrFiredancerTriggeredAbility() { super(Zone.BATTLEFIELD, new SatyrFiredancerDamageEffect(), false); @@ -115,22 +115,26 @@ class SatyrFiredancerTriggeredAbility extends TriggeredAbilityImpl { handledStackObjects.clear(); } + @Override + public boolean checkEventType(GameEvent event, Game game) { + return event.getType() == EventType.DAMAGED_PLAYER; + } + @Override public boolean checkTrigger(GameEvent event, Game game) { - if (event.getType() == EventType.DAMAGED_PLAYER) { - StackObject stackObject = game.getStack().getStackObject(event.getSourceId()); - if (stackObject != null && stackObject.getControllerId().equals(getControllerId())) { - MageObject object = game.getObject(stackObject.getSourceId()); - if (object.getCardType().contains(CardType.INSTANT) || object.getCardType().contains(CardType.SORCERY)) { - if (game.getOpponents(getControllerId()).contains(event.getTargetId())) { - if (!handledStackObjects.contains(stackObject.getId())) { - handledStackObjects.add(stackObject.getId()); - for (Effect effect: this.getEffects()) { - effect.setTargetPointer(new FixedTarget(event.getTargetId())); - effect.setValue("damage", event.getAmount()); - } - return true; + if (getControllerId().equals(game.getControllerId(event.getSourceId()))) { + MageObject damageSource = game.getObject(event.getSourceId()); + if (damageSource != null) { + if (game.getOpponents(getControllerId()).contains(event.getTargetId())) { + if (!(damageSource instanceof StackObject) || !handledStackObjects.contains(damageSource.getId())) { + if (damageSource instanceof StackObject) { + handledStackObjects.add(damageSource.getId()); } + for (Effect effect: this.getEffects()) { + effect.setTargetPointer(new FixedTarget(event.getTargetId())); // used by adjust targets + effect.setValue("damage", event.getAmount()); + } + return true; } } } diff --git a/Mage.Sets/src/mage/sets/championsofkamigawa/NightDealings.java b/Mage.Sets/src/mage/sets/championsofkamigawa/NightDealings.java index 9332a44fc13..4dc321dea1c 100644 --- a/Mage.Sets/src/mage/sets/championsofkamigawa/NightDealings.java +++ b/Mage.Sets/src/mage/sets/championsofkamigawa/NightDealings.java @@ -52,7 +52,6 @@ import mage.filter.predicate.mageobject.ConvertedManaCostPredicate; import mage.game.Game; import mage.game.events.GameEvent; import mage.game.permanent.Permanent; -import mage.game.stack.StackObject; import mage.players.Player; import mage.target.common.TargetCardInLibrary; @@ -65,7 +64,7 @@ public class NightDealings extends CardImpl { public NightDealings (UUID ownerId) { super(ownerId, 132, "Night Dealings", Rarity.RARE, new CardType[]{CardType.ENCHANTMENT}, "{2}{B}{B}"); this.expansionSetCode = "CHK"; - this.color.setBlack(true); + // Whenever a source you control deals damage to another player, put that many theft counters on Night Dealings. this.addAbility((new NightDealingsTriggeredAbility())); @@ -100,30 +99,24 @@ public class NightDealings extends CardImpl { return new NightDealingsTriggeredAbility(this); } + @Override + public boolean checkEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.DAMAGED_PLAYER; + } + @Override public boolean checkTrigger(GameEvent event, Game game) { - if (event.getType() == GameEvent.EventType.DAMAGED_PLAYER) { - // to another player - if (this.getControllerId() != event.getTargetId()) { - // a source you control - UUID sourceControllerId = null; - Permanent permanent = game.getPermanent(event.getSourceId()); - if (permanent != null) { - sourceControllerId = permanent.getControllerId(); - } else { - StackObject sourceStackObject = game.getStack().getStackObject(event.getSourceId()); - if (sourceStackObject != null) { - sourceControllerId = sourceStackObject.getControllerId(); - } - } - if (sourceControllerId != null && sourceControllerId == this.getControllerId()) { - // save amount of damage to effect - this.getEffects().get(0).setValue("damageAmount", event.getAmount()); - return true; - } + // to another player + if (this.getControllerId() != event.getTargetId()) { + // a source you control + UUID sourceControllerId = game.getControllerId(event.getSourceId()); + if (sourceControllerId != null && sourceControllerId == this.getControllerId()) { + // save amount of damage to effect + this.getEffects().get(0).setValue("damageAmount", event.getAmount()); + return true; } } - return false; + return false; } @Override @@ -136,7 +129,7 @@ public class NightDealings extends CardImpl { public NightDealingsEffect() { super(Outcome.Damage); - this.staticText = "put that many theft counters on Night Dealings"; + this.staticText = "put that many theft counters on {this}"; } public NightDealingsEffect(final NightDealingsEffect effect) { @@ -219,4 +212,4 @@ public class NightDealings extends CardImpl { } } -} \ No newline at end of file +} diff --git a/Mage.Sets/src/mage/sets/dragonsmaze/BlazeCommando.java b/Mage.Sets/src/mage/sets/dragonsmaze/BlazeCommando.java index 3eb68074c88..f728094c195 100644 --- a/Mage.Sets/src/mage/sets/dragonsmaze/BlazeCommando.java +++ b/Mage.Sets/src/mage/sets/dragonsmaze/BlazeCommando.java @@ -40,10 +40,12 @@ import mage.MageObject; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.effects.common.CreateTokenEffect; import mage.abilities.keyword.HasteAbility; +import mage.cards.Card; import mage.cards.CardImpl; import mage.game.Game; import mage.game.events.GameEvent; import mage.game.events.GameEvent.EventType; +import mage.game.permanent.Permanent; import mage.game.permanent.token.Token; import mage.game.stack.StackObject; @@ -82,7 +84,7 @@ public class BlazeCommando extends CardImpl { } class BlazeCommandoTriggeredAbility extends TriggeredAbilityImpl { - private List handledStackObjects = new ArrayList(); + private final List handledStackObjects = new ArrayList<>(); public BlazeCommandoTriggeredAbility() { super(Zone.BATTLEFIELD, new CreateTokenEffect(new BlazeCommandoSoldierToken(), 2), false); @@ -99,18 +101,30 @@ class BlazeCommandoTriggeredAbility extends TriggeredAbilityImpl { @Override public void reset(Game game) { + /** + * Blaze Commando's ability triggers each time an instant or sorcery spell you control + * deals damage (or, put another way, the number of times the word “deals” appears in + * its instructions), no matter how much damage is dealt or how many players or permanents + * are dealt damage. For example, if you cast Punish the Enemy and it “deals 3 damage to + * target player and 3 damage to target creature,” Blaze Commando's ability will trigger + * once and you'll get two Soldier tokens. + */ handledStackObjects.clear(); } + @Override + public boolean checkEventType(GameEvent event, Game game) { + return event.getType() == EventType.DAMAGED_CREATURE || event.getType() == EventType.DAMAGED_PLANESWALKER || event.getType() == EventType.DAMAGED_PLAYER; + } + @Override public boolean checkTrigger(GameEvent event, Game game) { - if (event.getType() == EventType.DAMAGED_CREATURE || event.getType() == EventType.DAMAGED_PLANESWALKER || event.getType() == EventType.DAMAGED_PLAYER) { - StackObject stackObject = game.getStack().getStackObject(event.getSourceId()); - if (stackObject != null && stackObject.getControllerId().equals(getControllerId())) { - MageObject object = game.getObject(stackObject.getSourceId()); - if (object.getCardType().contains(CardType.INSTANT) || object.getCardType().contains(CardType.SORCERY)) { - if (!handledStackObjects.contains(stackObject.getId())) { - handledStackObjects.add(stackObject.getId()); + if (getControllerId().equals(game.getControllerId(event.getSourceId()))) { + MageObject damageSource = game.getObject(event.getSourceId()); + if (damageSource != null) { + if (damageSource.getCardType().contains(CardType.INSTANT) || damageSource.getCardType().contains(CardType.SORCERY)) { + if (!handledStackObjects.contains(damageSource.getId())) { + handledStackObjects.add(damageSource.getId()); return true; } } diff --git a/Mage.Sets/src/mage/sets/magic2010/GuardianSeraph.java b/Mage.Sets/src/mage/sets/magic2010/GuardianSeraph.java index 1b6be3bfed5..22203d49b14 100644 --- a/Mage.Sets/src/mage/sets/magic2010/GuardianSeraph.java +++ b/Mage.Sets/src/mage/sets/magic2010/GuardianSeraph.java @@ -40,8 +40,6 @@ import mage.abilities.keyword.FlyingAbility; import mage.cards.CardImpl; import mage.game.Game; import mage.game.events.GameEvent; -import mage.game.permanent.Permanent; -import mage.game.stack.StackObject; /** * @@ -54,11 +52,11 @@ public class GuardianSeraph extends CardImpl { this.expansionSetCode = "M10"; this.subtype.add("Angel"); - this.color.setWhite(true); this.power = new MageInt(3); this.toughness = new MageInt(4); this.addAbility(FlyingAbility.getInstance()); + // If a source an opponent controls would deal damage to you, prevent 1 of that damage. this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new GuardianSeraphEffect())); } @@ -76,47 +74,24 @@ public class GuardianSeraph extends CardImpl { class GuardianSeraphEffect extends PreventionEffectImpl { public GuardianSeraphEffect() { - super(Duration.WhileOnBattlefield); + super(Duration.WhileOnBattlefield, 1, false, false); this.staticText = "If a source an opponent controls would deal damage to you, prevent 1 of that damage"; } - public GuardianSeraphEffect(GuardianSeraphEffect effect) { + public GuardianSeraphEffect(final GuardianSeraphEffect effect) { super(effect); } @Override - public boolean replaceEvent(GameEvent event, Ability source, Game game) { - GameEvent preventEvent = new GameEvent(GameEvent.EventType.PREVENT_DAMAGE, source.getFirstTarget(), source.getSourceId(), source.getControllerId(), 1, false); - if (!game.replaceEvent(preventEvent)) { - int damage = event.getAmount(); - if (damage > 0) { - event.setAmount(damage - 1); - game.informPlayers("1 damage has been prevented."); - } - game.fireEvent(GameEvent.getEvent(GameEvent.EventType.PREVENTED_DAMAGE, source.getFirstTarget(), source.getSourceId(), source.getControllerId(), 1)); - } - return false; + public boolean checksEventType(GameEvent event, Game game) { + return event.getType().equals(GameEvent.EventType.DAMAGE_PLAYER); } - - @Override - public boolean apply(Game game, Ability source) { - return true; - } - + @Override public boolean applies(GameEvent event, Ability source, Game game) { - if (event.getType().equals(GameEvent.EventType.DAMAGE_PLAYER)) { - UUID sourceControllerId = null; - Permanent permanent = game.getPermanent(event.getSourceId()); - if (permanent != null) { - sourceControllerId = permanent.getControllerId(); - } else { - StackObject sourceStackObject = game.getStack().getStackObject(event.getSourceId()); - if (sourceStackObject != null) { - sourceControllerId = sourceStackObject.getControllerId(); - } - } - if (event.getTargetId().equals(source.getControllerId()) && game.getOpponents(source.getControllerId()).contains(sourceControllerId)) { + if (event.getTargetId().equals(source.getControllerId())) { + UUID sourceControllerId = game.getControllerId(event.getSourceId()); + if (sourceControllerId != null && game.getOpponents(source.getControllerId()).contains(sourceControllerId)) { return super.applies(event, source, game); } } diff --git a/Mage.Sets/src/mage/sets/mirrodin/FarsightMask.java b/Mage.Sets/src/mage/sets/mirrodin/FarsightMask.java index b7133350056..7aa5166950c 100644 --- a/Mage.Sets/src/mage/sets/mirrodin/FarsightMask.java +++ b/Mage.Sets/src/mage/sets/mirrodin/FarsightMask.java @@ -29,20 +29,14 @@ package mage.sets.mirrodin; import java.util.UUID; import mage.constants.CardType; -import mage.constants.Outcome; import mage.constants.Rarity; import mage.constants.Zone; -import mage.abilities.Ability; import mage.abilities.TriggeredAbilityImpl; -import mage.abilities.condition.Condition; -import mage.abilities.decorator.ConditionalOneShotEffect; import mage.abilities.effects.common.DrawCardSourceControllerEffect; import mage.cards.CardImpl; import mage.game.Game; import mage.game.events.GameEvent; import mage.game.permanent.Permanent; -import mage.game.stack.StackObject; -import mage.players.Player; /** * @@ -71,7 +65,7 @@ public class FarsightMask extends CardImpl { class FarsightMaskTriggeredAbility extends TriggeredAbilityImpl { public FarsightMaskTriggeredAbility() { - super(Zone.BATTLEFIELD, new ConditionalOneShotEffect(new DrawCardSourceControllerEffect(1), new FarsightMaskCondition(), ""), false); + super(Zone.BATTLEFIELD, new DrawCardSourceControllerEffect(1), true); } public FarsightMaskTriggeredAbility(final FarsightMaskTriggeredAbility ability) { @@ -83,20 +77,22 @@ class FarsightMaskTriggeredAbility extends TriggeredAbilityImpl { return new FarsightMaskTriggeredAbility(this); } + @Override + public boolean checkEventType(GameEvent event, Game game) { + return event.getType().equals(GameEvent.EventType.DAMAGED_PLAYER); + } + + @Override + public boolean checkInterveningIfClause(Game game) { + Permanent permanent = game.getPermanent(getSourceId()); + return permanent != null && !permanent.isTapped(); + } + @Override public boolean checkTrigger(GameEvent event, Game game) { - if (event.getType().equals(GameEvent.EventType.DAMAGED_PLAYER)) { - UUID sourceControllerId = null; - Permanent permanent = game.getPermanent(event.getSourceId()); - if (permanent != null) { - sourceControllerId = permanent.getControllerId(); - } else { - StackObject sourceStackObject = game.getStack().getStackObject(event.getSourceId()); - if (sourceStackObject != null) { - sourceControllerId = sourceStackObject.getControllerId(); - } - } - if (event.getTargetId().equals(controllerId) && game.getOpponents(controllerId).contains(sourceControllerId)) { + if (event.getTargetId().equals(controllerId)) { + UUID sourceControllerId = game.getControllerId(event.getSourceId()); + if (sourceControllerId != null && game.getOpponents(getControllerId()).contains(sourceControllerId)) { return true; } } @@ -105,20 +101,6 @@ class FarsightMaskTriggeredAbility extends TriggeredAbilityImpl { @Override public String getRule() { - return "Whenever a source an opponent controls deals damage to you, if Farsight Mask is untapped, you may draw a card."; + return "Whenever a source an opponent controls deals damage to you, if {this} is untapped, you may draw a card."; } } - -class FarsightMaskCondition implements Condition { - - @Override - public boolean apply(Game game, Ability source) { - Permanent permanent = game.getPermanent(source.getSourceId()); - Player player = game.getPlayer(source.getControllerId()); - if (permanent != null && !permanent.isTapped() - && player != null && player.chooseUse(Outcome.DrawCard, "Do you wish to draw a card?", game)) { - return true; - } - return false; - } -} \ No newline at end of file diff --git a/Mage.Sets/src/mage/sets/modernmasters/PyromancersSwath.java b/Mage.Sets/src/mage/sets/modernmasters/PyromancersSwath.java index b1f71a9606f..90781a35f0e 100644 --- a/Mage.Sets/src/mage/sets/modernmasters/PyromancersSwath.java +++ b/Mage.Sets/src/mage/sets/modernmasters/PyromancersSwath.java @@ -54,8 +54,6 @@ public class PyromancersSwath extends CardImpl { super(ownerId, 125, "Pyromancer's Swath", Rarity.RARE, new CardType[]{CardType.ENCHANTMENT}, "{2}{R}"); this.expansionSetCode = "MMA"; - this.color.setRed(true); - // If an instant or sorcery source you control would deal damage to a creature or player, it deals that much damage plus 2 to that creature or player instead. this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new PyromancersSwathReplacementEffect())); @@ -98,14 +96,10 @@ class PyromancersSwathReplacementEffect extends ReplacementEffectImpl { } @Override - public boolean applies(GameEvent event, Ability source, Game game) { - MageObject object = game.getObject(event.getSourceId()); - if (object != null && object instanceof Spell) { - if (((Spell) object).getControllerId().equals(source.getControllerId()) - && (object.getCardType().contains(CardType.INSTANT) - || object.getCardType().contains(CardType.SORCERY))){ - return true; - } + public boolean applies(GameEvent event, Ability source, Game game) { + if (source.getControllerId().equals(game.getControllerId(event.getSourceId()))) { + MageObject object = game.getObject(event.getSourceId()); + return object != null && (object.getCardType().contains(CardType.INSTANT) || object.getCardType().contains(CardType.SORCERY)); } return false; } diff --git a/Mage.Sets/src/mage/sets/saviorsofkamigawa/MichikoKondaTruthSeeker.java b/Mage.Sets/src/mage/sets/saviorsofkamigawa/MichikoKondaTruthSeeker.java index 15c81b4d37c..40aad8c5e95 100644 --- a/Mage.Sets/src/mage/sets/saviorsofkamigawa/MichikoKondaTruthSeeker.java +++ b/Mage.Sets/src/mage/sets/saviorsofkamigawa/MichikoKondaTruthSeeker.java @@ -38,8 +38,6 @@ import mage.cards.CardImpl; import mage.filter.FilterPermanent; import mage.game.Game; import mage.game.events.GameEvent; -import mage.game.permanent.Permanent; -import mage.game.stack.StackObject; import mage.target.targetpointer.FixedTarget; /** @@ -55,7 +53,6 @@ public class MichikoKondaTruthSeeker extends CardImpl { this.subtype.add("Human"); this.subtype.add("Advisor"); - this.color.setWhite(true); this.power = new MageInt(2); this.toughness = new MageInt(2); @@ -87,21 +84,18 @@ class MichikoKondaTruthSeekerAbility extends TriggeredAbilityImpl { public MichikoKondaTruthSeekerAbility copy() { return new MichikoKondaTruthSeekerAbility(this); } - + + @Override + public boolean checkEventType(GameEvent event, Game game) { + return event.getType().equals(GameEvent.EventType.DAMAGED_PLAYER); + } + @Override public boolean checkTrigger(GameEvent event, Game game) { - if (event.getType().equals(GameEvent.EventType.DAMAGED_PLAYER)) { - UUID sourceControllerId = null; - Permanent permanent = game.getPermanent(event.getSourceId()); - if (permanent != null) { - sourceControllerId = permanent.getControllerId(); - } else { - StackObject sourceStackObject = game.getStack().getStackObject(event.getSourceId()); - if (sourceStackObject != null) { - sourceControllerId = sourceStackObject.getControllerId(); - } - } - if (event.getTargetId().equals(controllerId) && game.getOpponents(controllerId).contains(sourceControllerId)) { + if (event.getTargetId().equals(getControllerId())) { + UUID sourceControllerId = game.getControllerId(event.getSourceId()); + if (sourceControllerId != null && + game.getOpponents(getControllerId()).contains(sourceControllerId)) { getEffects().get(0).setTargetPointer(new FixedTarget(sourceControllerId)); return true; } diff --git a/Mage.Sets/src/mage/sets/zendikar/QuestForPureFlame.java b/Mage.Sets/src/mage/sets/zendikar/QuestForPureFlame.java index 95a4f09d1e5..1045a048dcb 100644 --- a/Mage.Sets/src/mage/sets/zendikar/QuestForPureFlame.java +++ b/Mage.Sets/src/mage/sets/zendikar/QuestForPureFlame.java @@ -44,9 +44,6 @@ import mage.constants.Zone; import mage.counters.CounterType; import mage.game.Game; import mage.game.events.GameEvent; -import mage.game.permanent.Permanent; -import mage.game.stack.StackObject; -import mage.players.Player; /** * @@ -95,27 +92,15 @@ class QuestForPureFlameTriggeredAbility extends TriggeredAbilityImpl { @Override public boolean checkTrigger(GameEvent event, Game game) { if (event.getType().equals(GameEvent.EventType.DAMAGED_PLAYER) - && game.getOpponents(controllerId).contains(event.getTargetId())) { - Permanent permanent = game.getPermanent(event.getSourceId()); - Player player = game.getPlayer(event.getSourceId()); - StackObject spell = game.getStack().getStackObject(event.getSourceId()); - if (permanent != null && permanent.getControllerId().equals(controllerId)) { - return true; - } - if (player != null && player.getId().equals(controllerId)) { - return true; - } - if (spell != null && spell.getControllerId().equals(controllerId)) { - return true; - } - return false; + && game.getOpponents(getControllerId()).contains(event.getTargetId())) { + return getControllerId().equals(game.getControllerId(event.getSourceId())); } return false; } @Override public String getRule() { - return "Whenever a source you control deals damage to an opponent, you may put a quest counter on Quest for Pure Flame."; + return "Whenever a source you control deals damage to an opponent, you may put a quest counter on {this}."; } } @@ -143,24 +128,7 @@ class QuestForPureFlameEffect extends ReplacementEffectImpl { @Override public boolean applies(GameEvent event, Ability source, Game game) { - Permanent permanent = game.getPermanent(event.getSourceId()); - if (permanent != null && permanent.getControllerId().equals(source.getControllerId())) { - return true; - } - Player player = game.getPlayer(event.getSourceId()); - if (player != null && player.getId().equals(source.getControllerId())) { - return true; - } - StackObject spell = game.getStack().getStackObject(event.getSourceId()); - if (spell != null && spell.getControllerId().equals(source.getControllerId())) { - return true; - } - return false; - } - - @Override - public boolean apply(Game game, Ability source) { - return true; + return source.getControllerId().equals(game.getControllerId(event.getSourceId())); } @Override diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/oneshot/damage/DeflectingPalmTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/oneshot/damage/DeflectingPalmTest.java new file mode 100644 index 00000000000..7a18095eb14 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/oneshot/damage/DeflectingPalmTest.java @@ -0,0 +1,80 @@ +/* + * 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.oneshot.damage; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * + * @author LevelX2 + */ +public class DeflectingPalmTest extends CardTestPlayerBase { + + /** + * Test that prevented damage will be created with the correct source and + * will trigger the ability of Satyr Firedance + * https://github.com/magefree/mage/issues/804 + */ + + @Test + public void testDamageInPlayer() { + addCard(Zone.BATTLEFIELD, playerA, "Mountain"); + addCard(Zone.BATTLEFIELD, playerA, "Plains"); + // The next time a source of your choice would deal damage to you this turn, prevent that damage. + // If damage is prevented this way, Deflecting Palm deals that much damage to that source's controller. + addCard(Zone.HAND, playerA, "Deflecting Palm"); + // Whenever an instant or sorcery spell you control deals damage to an opponent, Satyr Firedancer deals + // that much damage to target creature that player controls. + addCard(Zone.BATTLEFIELD, playerA, "Satyr Firedancer"); + + addCard(Zone.BATTLEFIELD, playerB, "Silvercoat Lion"); + addCard(Zone.BATTLEFIELD, playerB, "Mountain"); + addCard(Zone.HAND, playerB, "Lightning Bolt"); + + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB ,"Lightning Bolt", playerA); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA ,"Deflecting Palm", null, "Lightning Bolt"); + setChoice(playerA, "Lightning Bolt"); + addTarget(playerA, "Silvercoat Lion"); // target for Satyr Firedancer + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertGraveyardCount(playerA, "Deflecting Palm", 1); + assertGraveyardCount(playerB, "Lightning Bolt", 1); + + + assertGraveyardCount(playerB, "Silvercoat Lion", 1); + + assertLife(playerA, 20); + assertLife(playerB, 17); + } +} \ No newline at end of file diff --git a/Mage/src/mage/abilities/effects/ContinuousEffects.java b/Mage/src/mage/abilities/effects/ContinuousEffects.java index c7426d4181a..7b2f8722caf 100644 --- a/Mage/src/mage/abilities/effects/ContinuousEffects.java +++ b/Mage/src/mage/abilities/effects/ContinuousEffects.java @@ -1099,6 +1099,37 @@ public class ContinuousEffects implements Serializable { public boolean existRequirementEffects() { return !requirementEffects.isEmpty(); } + + public UUID getControllerOfSourceId(UUID sourceId) { + UUID controllerFound = null; + for (PreventionEffect effect: preventionEffects) { + HashSet abilities = preventionEffects.getAbility(effect.getId()); + for (Ability ability : abilities) { + if (ability.getSourceId().equals(sourceId)) { + if (controllerFound == null || controllerFound == ability.getControllerId()) { + controllerFound = ability.getControllerId(); + } else { + // not unique controller - No solution yet + return null; + } + } + } + } + for (ReplacementEffect effect: replacementEffects) { + HashSet abilities = replacementEffects.getAbility(effect.getId()); + for (Ability ability : abilities) { + if (ability.getSourceId().equals(sourceId)) { + if (controllerFound == null || controllerFound == ability.getControllerId()) { + controllerFound = ability.getControllerId(); + } else { + // not unique controller - No solution yet + return null; + } + } + } + } + return controllerFound; + } } class ContinuousEffectSorter implements Comparator { diff --git a/Mage/src/mage/game/Game.java b/Mage/src/mage/game/Game.java index 0d59775657a..817c65571c4 100644 --- a/Mage/src/mage/game/Game.java +++ b/Mage/src/mage/game/Game.java @@ -293,4 +293,5 @@ public interface Game extends MageItem, Serializable { int getPriorityTime(); void setPriorityTime(int priorityTime); UUID getStartingPlayerId(); + } diff --git a/Mage/src/mage/game/GameImpl.java b/Mage/src/mage/game/GameImpl.java index 3e7d9a252fe..1c3275e7e2d 100644 --- a/Mage/src/mage/game/GameImpl.java +++ b/Mage/src/mage/game/GameImpl.java @@ -382,9 +382,23 @@ public abstract class GameImpl implements Game, Serializable { } MageObject object = getObject(objectId); if (object != null) { - if (object instanceof Permanent) { + if (object instanceof StackObject) { + return ((StackObject)object).getControllerId(); + } else if (object instanceof Permanent) { return ((Permanent)object).getControllerId(); + } else if (object instanceof CommandObject) { + return ((CommandObject)object).getControllerId(); + } + UUID controllerId = getContinuousEffects().getControllerOfSourceId(objectId); + if (controllerId != null) { + return controllerId; } + // TODO: When is a player the damage source itself? If not possible remove this + Player player = getPlayer(objectId); + if (player != null){ + return player.getId(); + } + // No object with controller found so return owner if possible if (object instanceof Card) { return ((Card)object).getOwnerId(); } @@ -2500,4 +2514,6 @@ public abstract class GameImpl implements Game, Serializable { } } + + } diff --git a/Mage/src/mage/game/events/GameEvent.java b/Mage/src/mage/game/events/GameEvent.java index 06838a6e656..662d13af589 100644 --- a/Mage/src/mage/game/events/GameEvent.java +++ b/Mage/src/mage/game/events/GameEvent.java @@ -93,7 +93,17 @@ public class GameEvent { DISCARDED_CARD, CYCLE_CARD, CYCLED_CARD, CLASH, CLASHED, - DAMAGE_PLAYER, DAMAGED_PLAYER, + DAMAGE_PLAYER, + + /* DAMAGED_PLAYER + targetId the id of the damged player + sourceId sourceId of the ability which caused the damage + playerId the id of the damged player + amount amount of damage + flag true = comabat damage - other damage = false + */ + DAMAGED_PLAYER, + DAMAGE_CAUSES_LIFE_LOSS, PLAYER_LIFE_CHANGE, GAIN_LIFE, GAINED_LIFE,