From 5fbdca0b28a7b02e975f97b4f4887a4f1bd00a54 Mon Sep 17 00:00:00 2001 From: Zzooouhh Date: Sun, 15 Oct 2017 16:17:22 +0200 Subject: [PATCH 01/16] Fixed blocker selection bug The prior version didn't allow you to pick blockers if you weren't the attacking player (for example, in an EDH game if you activate this during another players turn and you're not the defender) --- Mage.Sets/src/mage/cards/b/BrutalHordechief.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Mage.Sets/src/mage/cards/b/BrutalHordechief.java b/Mage.Sets/src/mage/cards/b/BrutalHordechief.java index 9add26359b4..3311459c095 100644 --- a/Mage.Sets/src/mage/cards/b/BrutalHordechief.java +++ b/Mage.Sets/src/mage/cards/b/BrutalHordechief.java @@ -151,7 +151,11 @@ class BrutalHordechiefReplacementEffect extends ReplacementEffectImpl { @Override public boolean applies(GameEvent event, Ability source, Game game) { - return event.getPlayerId().equals(source.getControllerId()); + Player blockController = game.getPlayer(source.getControllerId()); + if (blockController != null) { + return true; + } + return false; } @Override From f5569f9c0a0b53472a713c62a0e3178ce4642164 Mon Sep 17 00:00:00 2001 From: Zzooouhh Date: Sun, 15 Oct 2017 16:20:14 +0200 Subject: [PATCH 02/16] Implemented Master Warcraft There's still line 190 left to fix... --- .../src/mage/cards/m/MasterWarcraft.java | 249 ++++++++++++++++++ 1 file changed, 249 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/m/MasterWarcraft.java diff --git a/Mage.Sets/src/mage/cards/m/MasterWarcraft.java b/Mage.Sets/src/mage/cards/m/MasterWarcraft.java new file mode 100644 index 00000000000..4ad9242b8a0 --- /dev/null +++ b/Mage.Sets/src/mage/cards/m/MasterWarcraft.java @@ -0,0 +1,249 @@ +/* + * 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.cards.m; + +import java.util.UUID; +import mage.abilities.Ability; +import mage.abilities.Mode; +import mage.abilities.common.CastOnlyDuringPhaseStepSourceAbility; +import mage.abilities.common.delayed.AtTheBeginOfNextUpkeepDelayedTriggeredAbility; +import mage.abilities.condition.common.BeforeAttackersAreDeclaredCondition; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.ReplacementEffectImpl; +import mage.abilities.effects.RequirementEffect; +import mage.abilities.effects.RestrictionEffect; +import mage.abilities.effects.common.CreateDelayedTriggeredAbilityEffect; +import mage.abilities.effects.common.combat.AttacksIfAbleTargetEffect; +import mage.abilities.effects.common.combat.CantAttackTargetEffect; +import mage.abilities.effects.common.combat.CantBlockTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.filter.common.FilterCreaturePermanent; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.events.GameEvent.EventType; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.Target; +import mage.target.common.TargetCreaturePermanent; +import mage.target.targetpointer.FixedTarget; + +/** + * + * @author L_J + */ +public class MasterWarcraft extends CardImpl { + + public MasterWarcraft(UUID ownerId, CardSetInfo setInfo) { + super(ownerId,setInfo,new CardType[]{CardType.INSTANT},"{2}{R/W}{R/W}"); + + // Cast Master Warcraft only before attackers are declared. + this.addAbility(new CastOnlyDuringPhaseStepSourceAbility(null, null, BeforeAttackersAreDeclaredCondition.instance)); + + // You choose which creatures attack this turn. + this.getSpellAbility().addEffect(new MasterWarcraftChooseAttackersEffect().setText("You choose which creatures attack this turn.")); + + // You choose which creatures block this turn and how those creatures block. + this.getSpellAbility().addEffect(new MasterWarcraftChooseBlockersEffect().setText("You choose which creatures block this turn and how those creatures block.")); + } + + public MasterWarcraft(final MasterWarcraft card) { + super(card); + } + + @Override + public MasterWarcraft copy() { + return new MasterWarcraft(this); + } +} + +class MasterWarcraftChooseAttackersEffect extends ReplacementEffectImpl { + + public MasterWarcraftChooseAttackersEffect() { + super(Duration.EndOfTurn, Outcome.Benefit); + this.staticText = "You choose which creatures attack this turn."; + } + + public MasterWarcraftChooseAttackersEffect(final MasterWarcraftChooseAttackersEffect effect) { + super(effect); + } + + @Override + public MasterWarcraftChooseAttackersEffect copy() { + return new MasterWarcraftChooseAttackersEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + return true; + } + + @Override + public boolean replaceEvent(GameEvent event, Ability source, Game game) { + Player chooser = game.getPlayer(source.getControllerId()); + if (chooser != null) { + new MasterWarcraftAttackEffect().apply(game, source); // Master Warcraft imposes its effect right before the attackers being declared... + } + return false; // ...and then resumes the attack declaration + } + + @Override + public boolean checksEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.DECLARING_ATTACKERS; + } + + @Override + public boolean applies(GameEvent event, Ability source, Game game) { + Player chooser = game.getPlayer(source.getControllerId()); + Player attackingPlayer = game.getPlayer(game.getCombat().getAttackingPlayerId()); + if (chooser != null && attackingPlayer != null && !attackingPlayer.getAvailableAttackers(game).isEmpty()) { + return true; + } + return false; + } +} + +class MasterWarcraftAttackEffect extends OneShotEffect { + + MasterWarcraftAttackEffect() { + super(Outcome.Benefit); + } + + MasterWarcraftAttackEffect(final MasterWarcraftAttackEffect effect) { + super(effect); + } + + @Override + public MasterWarcraftAttackEffect copy() { + return new MasterWarcraftAttackEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + if (controller != null) { + Target target = new TargetCreaturePermanent(0, Integer.MAX_VALUE, new FilterCreaturePermanent("creatures that will attack this combat (creatures not chosen won't attack this combat)"), true); + if (target.choose(Outcome.Neutral, source.getControllerId(), source.getSourceId(), game)) { + for (Permanent permanent : game.getBattlefield().getActivePermanents(new FilterCreaturePermanent(), source.getControllerId(), source.getSourceId(), game)) { + if (target.getTargets().contains(permanent.getId())) { + RequirementEffect effect = new AttacksIfAbleTargetEffect(Duration.EndOfCombat); + effect.setText(""); + effect.setTargetPointer(new FixedTarget(permanent.getId())); + game.addEffect(effect, source); + } else { + RestrictionEffect effect = new MasterWarcraftCantAttackRestrictionEffect(); + effect.setText(""); + effect.setTargetPointer(new FixedTarget(permanent.getId())); + game.addEffect(effect, source); + } + } + return true; + } + } + return false; + } +} + +class MasterWarcraftCantAttackRestrictionEffect extends RestrictionEffect { + + MasterWarcraftCantAttackRestrictionEffect() { + super(Duration.EndOfCombat); + } + + MasterWarcraftCantAttackRestrictionEffect(final MasterWarcraftCantAttackRestrictionEffect effect) { + super(effect); + } + + @Override + public MasterWarcraftCantAttackRestrictionEffect copy() { + return new MasterWarcraftCantAttackRestrictionEffect(this); + } + + @Override + public boolean applies(Permanent permanent, Ability source, Game game) { + // TODO: Make Master Warcraft still respect "This must attack if able" clauses + return this.getTargetPointer().getFirst(game, source).equals(permanent.getId()); + } + + @Override + public boolean canAttack(Game game) { + return false; + } + + @Override + public String getText(Mode mode) { + return "Unless {this} must attack, {this} can't attack."; + } +} + +class MasterWarcraftChooseBlockersEffect extends ReplacementEffectImpl { + + public MasterWarcraftChooseBlockersEffect() { + super(Duration.EndOfTurn, Outcome.Benefit); + staticText = "You choose which creatures block this turn and how those creatures block."; + } + + public MasterWarcraftChooseBlockersEffect(final MasterWarcraftChooseBlockersEffect effect) { + super(effect); + } + + @Override + public MasterWarcraftChooseBlockersEffect copy() { + return new MasterWarcraftChooseBlockersEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + return false; + } + + @Override + public boolean checksEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.DECLARING_BLOCKERS; + } + + @Override + public boolean applies(GameEvent event, Ability source, Game game) { + Player blockController = game.getPlayer(source.getControllerId()); + if (blockController != null) { + return true; + } + return false; + } + + @Override + public boolean replaceEvent(GameEvent event, Ability source, Game game) { + Player blockController = game.getPlayer(source.getControllerId()); + if (blockController != null) { + game.getCombat().selectBlockers(blockController, game); + return true; + } + return false; + } +} From 9563ea84be4ea631882deddf13c898d8ab31086f Mon Sep 17 00:00:00 2001 From: Zzooouhh Date: Sun, 15 Oct 2017 16:21:34 +0200 Subject: [PATCH 03/16] Implemented Master Warcraft --- Mage.Sets/src/mage/sets/RavnicaCityOfGuilds.java | 1 + 1 file changed, 1 insertion(+) diff --git a/Mage.Sets/src/mage/sets/RavnicaCityOfGuilds.java b/Mage.Sets/src/mage/sets/RavnicaCityOfGuilds.java index 8bea79044a2..f2884e0aa52 100644 --- a/Mage.Sets/src/mage/sets/RavnicaCityOfGuilds.java +++ b/Mage.Sets/src/mage/sets/RavnicaCityOfGuilds.java @@ -213,6 +213,7 @@ public class RavnicaCityOfGuilds extends ExpansionSet { cards.add(new SetCardInfo("Loxodon Hierarch", 214, Rarity.RARE, mage.cards.l.LoxodonHierarch.class)); cards.add(new SetCardInfo("Lurking Informant", 249, Rarity.COMMON, mage.cards.l.LurkingInformant.class)); cards.add(new SetCardInfo("Mark of Eviction", 58, Rarity.UNCOMMON, mage.cards.m.MarkOfEviction.class)); + cards.add(new SetCardInfo("Master Warcraft", 250, Rarity.RARE, mage.cards.m.MasterWarcraft.class)); cards.add(new SetCardInfo("Mausoleum Turnkey", 94, Rarity.UNCOMMON, mage.cards.m.MausoleumTurnkey.class)); cards.add(new SetCardInfo("Mindleech Mass", 215, Rarity.RARE, mage.cards.m.MindleechMass.class)); cards.add(new SetCardInfo("Mindmoil", 135, Rarity.RARE, mage.cards.m.Mindmoil.class)); From cc95002beeaa8a585f8b0ea844de116728ce1cfd Mon Sep 17 00:00:00 2001 From: Zzooouhh Date: Sun, 15 Oct 2017 16:23:18 +0200 Subject: [PATCH 04/16] Implemented Master Warcraft --- Mage.Sets/src/mage/sets/Commander.java | 1 + 1 file changed, 1 insertion(+) diff --git a/Mage.Sets/src/mage/sets/Commander.java b/Mage.Sets/src/mage/sets/Commander.java index a87566d11eb..3460d426797 100644 --- a/Mage.Sets/src/mage/sets/Commander.java +++ b/Mage.Sets/src/mage/sets/Commander.java @@ -210,6 +210,7 @@ public class Commander extends ExpansionSet { cards.add(new SetCardInfo("Malfegor", 208, Rarity.MYTHIC, mage.cards.m.Malfegor.class)); cards.add(new SetCardInfo("Mana-Charged Dragon", 129, Rarity.RARE, mage.cards.m.ManaChargedDragon.class)); cards.add(new SetCardInfo("Martyr's Bond", 19, Rarity.RARE, mage.cards.m.MartyrsBond.class)); + cards.add(new SetCardInfo("Master Warcraft", 209, Rarity.RARE, mage.cards.m.MasterWarcraft.class)); cards.add(new SetCardInfo("Memory Erosion", 50, Rarity.RARE, mage.cards.m.MemoryErosion.class)); cards.add(new SetCardInfo("Minds Aglow", 51, Rarity.RARE, mage.cards.m.MindsAglow.class)); cards.add(new SetCardInfo("Molten Slagheap", 282, Rarity.UNCOMMON, mage.cards.m.MoltenSlagheap.class)); From 5e25d77eda5cb5734cb5e68120de48300e89fd8e Mon Sep 17 00:00:00 2001 From: Zzooouhh Date: Sun, 15 Oct 2017 16:23:26 +0200 Subject: [PATCH 05/16] Implemented Master Warcraft --- Mage.Sets/src/mage/sets/CommanderAnthology.java | 1 + 1 file changed, 1 insertion(+) diff --git a/Mage.Sets/src/mage/sets/CommanderAnthology.java b/Mage.Sets/src/mage/sets/CommanderAnthology.java index fc943136b22..7142cee09ae 100644 --- a/Mage.Sets/src/mage/sets/CommanderAnthology.java +++ b/Mage.Sets/src/mage/sets/CommanderAnthology.java @@ -207,6 +207,7 @@ public class CommanderAnthology extends ExpansionSet { cards.add(new SetCardInfo("Malfegor", 184, Rarity.MYTHIC, mage.cards.m.Malfegor.class)); cards.add(new SetCardInfo("Mana-Charged Dragon", 84, Rarity.RARE, mage.cards.m.ManaChargedDragon.class)); cards.add(new SetCardInfo("Masked Admirers", 127, Rarity.UNCOMMON, mage.cards.m.MaskedAdmirers.class)); + cards.add(new SetCardInfo("Master Warcraft", 202, Rarity.RARE, mage.cards.m.MasterWarcraft.class)); cards.add(new SetCardInfo("Mazirek, Kraul Death Priest", 185, Rarity.MYTHIC, mage.cards.m.MazirekKraulDeathPriest.class)); cards.add(new SetCardInfo("Meren of Clan Nel Toth", 186, Rarity.MYTHIC, mage.cards.m.MerenOfClanNelToth.class)); cards.add(new SetCardInfo("Mirror Entity", 16, Rarity.RARE, mage.cards.m.MirrorEntity.class)); From 56d93b0def77825c3f8edbc395e643de581ab0a6 Mon Sep 17 00:00:00 2001 From: Zzooouhh Date: Sun, 15 Oct 2017 16:41:15 +0200 Subject: [PATCH 06/16] Imports cleanup --- Mage.Sets/src/mage/cards/m/MasterWarcraft.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/Mage.Sets/src/mage/cards/m/MasterWarcraft.java b/Mage.Sets/src/mage/cards/m/MasterWarcraft.java index 4ad9242b8a0..6cbe123eacd 100644 --- a/Mage.Sets/src/mage/cards/m/MasterWarcraft.java +++ b/Mage.Sets/src/mage/cards/m/MasterWarcraft.java @@ -31,7 +31,6 @@ import java.util.UUID; import mage.abilities.Ability; import mage.abilities.Mode; import mage.abilities.common.CastOnlyDuringPhaseStepSourceAbility; -import mage.abilities.common.delayed.AtTheBeginOfNextUpkeepDelayedTriggeredAbility; import mage.abilities.condition.common.BeforeAttackersAreDeclaredCondition; import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.ReplacementEffectImpl; @@ -39,8 +38,6 @@ import mage.abilities.effects.RequirementEffect; import mage.abilities.effects.RestrictionEffect; import mage.abilities.effects.common.CreateDelayedTriggeredAbilityEffect; import mage.abilities.effects.common.combat.AttacksIfAbleTargetEffect; -import mage.abilities.effects.common.combat.CantAttackTargetEffect; -import mage.abilities.effects.common.combat.CantBlockTargetEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.*; From 4155b30af33d66dfa0e39e5e8abf12129bc0378f Mon Sep 17 00:00:00 2001 From: Zzooouhh Date: Sun, 15 Oct 2017 23:20:27 +0200 Subject: [PATCH 07/16] Added missing "mustn't must attack" clause & some text fixes --- Mage.Sets/src/mage/cards/m/MasterWarcraft.java | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/Mage.Sets/src/mage/cards/m/MasterWarcraft.java b/Mage.Sets/src/mage/cards/m/MasterWarcraft.java index 6cbe123eacd..4c9cae66745 100644 --- a/Mage.Sets/src/mage/cards/m/MasterWarcraft.java +++ b/Mage.Sets/src/mage/cards/m/MasterWarcraft.java @@ -27,7 +27,7 @@ */ package mage.cards.m; -import java.util.UUID; +import java.util.*; import mage.abilities.Ability; import mage.abilities.Mode; import mage.abilities.common.CastOnlyDuringPhaseStepSourceAbility; @@ -64,10 +64,10 @@ public class MasterWarcraft extends CardImpl { this.addAbility(new CastOnlyDuringPhaseStepSourceAbility(null, null, BeforeAttackersAreDeclaredCondition.instance)); // You choose which creatures attack this turn. - this.getSpellAbility().addEffect(new MasterWarcraftChooseAttackersEffect().setText("You choose which creatures attack this turn.")); + this.getSpellAbility().addEffect(new MasterWarcraftChooseAttackersEffect()); // You choose which creatures block this turn and how those creatures block. - this.getSpellAbility().addEffect(new MasterWarcraftChooseBlockersEffect().setText("You choose which creatures block this turn and how those creatures block.")); + this.getSpellAbility().addEffect(new MasterWarcraftChooseBlockersEffect()); } public MasterWarcraft(final MasterWarcraft card) { @@ -84,7 +84,7 @@ class MasterWarcraftChooseAttackersEffect extends ReplacementEffectImpl { public MasterWarcraftChooseAttackersEffect() { super(Duration.EndOfTurn, Outcome.Benefit); - this.staticText = "You choose which creatures attack this turn."; + this.staticText = "You choose which creatures attack this turn"; } public MasterWarcraftChooseAttackersEffect(final MasterWarcraftChooseAttackersEffect effect) { @@ -184,7 +184,12 @@ class MasterWarcraftCantAttackRestrictionEffect extends RestrictionEffect { @Override public boolean applies(Permanent permanent, Ability source, Game game) { - // TODO: Make Master Warcraft still respect "This must attack if able" clauses + for (Map.Entry> entry : game.getContinuousEffects().getApplicableRequirementEffects(creature, false, game).entrySet()) { + RequirementEffect effect = entry.getKey(); + if (effect.mustAttack(game)) { + return false; + } + } return this.getTargetPointer().getFirst(game, source).equals(permanent.getId()); } @@ -203,7 +208,7 @@ class MasterWarcraftChooseBlockersEffect extends ReplacementEffectImpl { public MasterWarcraftChooseBlockersEffect() { super(Duration.EndOfTurn, Outcome.Benefit); - staticText = "You choose which creatures block this turn and how those creatures block."; + staticText = "You choose which creatures block this turn and how those creatures block"; } public MasterWarcraftChooseBlockersEffect(final MasterWarcraftChooseBlockersEffect effect) { From 6400a401d4eac4744b324fccc16520701055fcf4 Mon Sep 17 00:00:00 2001 From: Zzooouhh Date: Sun, 15 Oct 2017 23:26:11 +0200 Subject: [PATCH 08/16] Oversight fix --- Mage.Sets/src/mage/cards/m/MasterWarcraft.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Mage.Sets/src/mage/cards/m/MasterWarcraft.java b/Mage.Sets/src/mage/cards/m/MasterWarcraft.java index 4c9cae66745..7216c62d059 100644 --- a/Mage.Sets/src/mage/cards/m/MasterWarcraft.java +++ b/Mage.Sets/src/mage/cards/m/MasterWarcraft.java @@ -183,14 +183,14 @@ class MasterWarcraftCantAttackRestrictionEffect extends RestrictionEffect { } @Override - public boolean applies(Permanent permanent, Ability source, Game game) { + public boolean applies(Permanent creature, Ability source, Game game) { for (Map.Entry> entry : game.getContinuousEffects().getApplicableRequirementEffects(creature, false, game).entrySet()) { RequirementEffect effect = entry.getKey(); if (effect.mustAttack(game)) { return false; } } - return this.getTargetPointer().getFirst(game, source).equals(permanent.getId()); + return this.getTargetPointer().getFirst(game, source).equals(creature.getId()); } @Override From e65eefe5ab89982dc651d02f99dac77b2d18f1ca Mon Sep 17 00:00:00 2001 From: Zzooouhh Date: Tue, 17 Oct 2017 17:01:10 +0200 Subject: [PATCH 09/16] Rewrote a lot of code ...to handle multiple conflicting instances of Master Warcraft (still yet to rewrite the blocker effect) --- .../src/mage/cards/m/MasterWarcraft.java | 86 +++++++++++++------ 1 file changed, 60 insertions(+), 26 deletions(-) diff --git a/Mage.Sets/src/mage/cards/m/MasterWarcraft.java b/Mage.Sets/src/mage/cards/m/MasterWarcraft.java index 7216c62d059..cfbf33e8c15 100644 --- a/Mage.Sets/src/mage/cards/m/MasterWarcraft.java +++ b/Mage.Sets/src/mage/cards/m/MasterWarcraft.java @@ -32,6 +32,7 @@ import mage.abilities.Ability; import mage.abilities.Mode; import mage.abilities.common.CastOnlyDuringPhaseStepSourceAbility; import mage.abilities.condition.common.BeforeAttackersAreDeclaredCondition; +import mage.abilities.effects.ContinuousRuleModifyingEffectImpl; import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.ReplacementEffectImpl; import mage.abilities.effects.RequirementEffect; @@ -58,7 +59,7 @@ import mage.target.targetpointer.FixedTarget; public class MasterWarcraft extends CardImpl { public MasterWarcraft(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.INSTANT},"{2}{R/W}{R/W}"); + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{2}{R/W}{R/W}"); // Cast Master Warcraft only before attackers are declared. this.addAbility(new CastOnlyDuringPhaseStepSourceAbility(null, null, BeforeAttackersAreDeclaredCondition.instance)); @@ -80,11 +81,11 @@ public class MasterWarcraft extends CardImpl { } } -class MasterWarcraftChooseAttackersEffect extends ReplacementEffectImpl { +class MasterWarcraftChooseAttackersEffect extends ContinuousRuleModifyingEffectImpl { public MasterWarcraftChooseAttackersEffect() { - super(Duration.EndOfTurn, Outcome.Benefit); - this.staticText = "You choose which creatures attack this turn"; + super(Duration.EndOfTurn, Outcome.Benefit, false, false); + staticText = "You choose which creatures attack this turn"; } public MasterWarcraftChooseAttackersEffect(final MasterWarcraftChooseAttackersEffect effect) { @@ -101,15 +102,6 @@ class MasterWarcraftChooseAttackersEffect extends ReplacementEffectImpl { return true; } - @Override - public boolean replaceEvent(GameEvent event, Ability source, Game game) { - Player chooser = game.getPlayer(source.getControllerId()); - if (chooser != null) { - new MasterWarcraftAttackEffect().apply(game, source); // Master Warcraft imposes its effect right before the attackers being declared... - } - return false; // ...and then resumes the attack declaration - } - @Override public boolean checksEventType(GameEvent event, Game game) { return event.getType() == GameEvent.EventType.DECLARING_ATTACKERS; @@ -120,9 +112,19 @@ class MasterWarcraftChooseAttackersEffect extends ReplacementEffectImpl { Player chooser = game.getPlayer(source.getControllerId()); Player attackingPlayer = game.getPlayer(game.getCombat().getAttackingPlayerId()); if (chooser != null && attackingPlayer != null && !attackingPlayer.getAvailableAttackers(game).isEmpty()) { - return true; + for (Permanent permanent : game.getBattlefield().getActivePermanents(new FilterCreaturePermanent(), source.getControllerId(), source.getSourceId(), game)) { + // Clears previous instances of "should attack" effects + // ("shouldn't attack" effects don't need cleaning because MasterWarcraftMustAttackRequirementEffect overrides them) + for (Map.Entry> entry : game.getContinuousEffects().getApplicableRequirementEffects(permanent, false, game).entrySet()) { + RequirementEffect effect = entry.getKey(); + if (effect instanceof MasterWarcraftMustAttackRequirementEffect) { + effect.discard(); + } + } + } + new MasterWarcraftAttackEffect().apply(game, source); // Master Warcraft imposes its effect right before the attackers being declared... } - return false; + return false; // ...and then resumes the attack declaration for the active player as normal } } @@ -145,20 +147,28 @@ class MasterWarcraftAttackEffect extends OneShotEffect { public boolean apply(Game game, Ability source) { Player controller = game.getPlayer(source.getControllerId()); if (controller != null) { + // TODO: find a way to undo creature selection Target target = new TargetCreaturePermanent(0, Integer.MAX_VALUE, new FilterCreaturePermanent("creatures that will attack this combat (creatures not chosen won't attack this combat)"), true); if (target.choose(Outcome.Neutral, source.getControllerId(), source.getSourceId(), game)) { for (Permanent permanent : game.getBattlefield().getActivePermanents(new FilterCreaturePermanent(), source.getControllerId(), source.getSourceId(), game)) { + + // Choose creatures that will be attacking this combat if (target.getTargets().contains(permanent.getId())) { - RequirementEffect effect = new AttacksIfAbleTargetEffect(Duration.EndOfCombat); + RequirementEffect effect = new MasterWarcraftMustAttackRequirementEffect(); effect.setText(""); effect.setTargetPointer(new FixedTarget(permanent.getId())); game.addEffect(effect, source); + // TODO: find a better way to report attackers to game log + // game.informPlayers(controller.getLogName() + " has decided that " + permanent.getLogName() + " should attack this combat if able"); + + // All other creatures can't attack } else { RestrictionEffect effect = new MasterWarcraftCantAttackRestrictionEffect(); effect.setText(""); effect.setTargetPointer(new FixedTarget(permanent.getId())); game.addEffect(effect, source); } + } return true; } @@ -167,6 +177,30 @@ class MasterWarcraftAttackEffect extends OneShotEffect { } } +class MasterWarcraftMustAttackRequirementEffect extends AttacksIfAbleTargetEffect { + + MasterWarcraftMustAttackRequirementEffect() { + super(Duration.EndOfCombat); + } + + MasterWarcraftMustAttackRequirementEffect(final MasterWarcraftMustAttackRequirementEffect effect) { + super(effect); + } + + @Override + public MasterWarcraftMustAttackRequirementEffect copy() { + return new MasterWarcraftMustAttackRequirementEffect(this); + } + + @Override + public boolean applies(Permanent permanent, Ability source, Game game) { + if (discarded) { + return false; + } + return this.getTargetPointer().getTargets(game, source).contains(permanent.getId()); + } +} + class MasterWarcraftCantAttackRestrictionEffect extends RestrictionEffect { MasterWarcraftCantAttackRestrictionEffect() { @@ -183,18 +217,18 @@ class MasterWarcraftCantAttackRestrictionEffect extends RestrictionEffect { } @Override - public boolean applies(Permanent creature, Ability source, Game game) { - for (Map.Entry> entry : game.getContinuousEffects().getApplicableRequirementEffects(creature, false, game).entrySet()) { - RequirementEffect effect = entry.getKey(); - if (effect.mustAttack(game)) { - return false; - } - } - return this.getTargetPointer().getFirst(game, source).equals(creature.getId()); + public boolean applies(Permanent permanent, Ability source, Game game) { + return this.getTargetPointer().getFirst(game, source).equals(permanent.getId()); } @Override - public boolean canAttack(Game game) { + public boolean canAttack(Permanent attacker, UUID defenderId, Ability source, Game game) { + for (Map.Entry> entry : game.getContinuousEffects().getApplicableRequirementEffects(attacker, false, game).entrySet()) { + RequirementEffect effect = entry.getKey(); + if (effect.mustAttack(game)) { + return true; + } + } return false; } @@ -204,7 +238,7 @@ class MasterWarcraftCantAttackRestrictionEffect extends RestrictionEffect { } } -class MasterWarcraftChooseBlockersEffect extends ReplacementEffectImpl { +class MasterWarcraftChooseBlockersEffect extends ReplacementEffectImpl { // TODO: replace this with ContinuousRuleModifyingEffectImpl public MasterWarcraftChooseBlockersEffect() { super(Duration.EndOfTurn, Outcome.Benefit); From 197aa33fc05fc59cf34dfe0fea8132debfbcb6d0 Mon Sep 17 00:00:00 2001 From: Zzooouhh Date: Wed, 18 Oct 2017 21:33:05 +0200 Subject: [PATCH 10/16] Removed Target Source redundancy --- Mage.Sets/src/mage/cards/o/OpalEyeKondasYojimbo.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Mage.Sets/src/mage/cards/o/OpalEyeKondasYojimbo.java b/Mage.Sets/src/mage/cards/o/OpalEyeKondasYojimbo.java index 23b9527c5f2..971e38c0d85 100644 --- a/Mage.Sets/src/mage/cards/o/OpalEyeKondasYojimbo.java +++ b/Mage.Sets/src/mage/cards/o/OpalEyeKondasYojimbo.java @@ -69,9 +69,7 @@ public class OpalEyeKondasYojimbo extends CardImpl { this.addAbility(new BushidoAbility(1)); // {T}: The next time a source of your choice would deal damage this turn, that damage is dealt to Opal-Eye, Konda's Yojimbo instead. - Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new OpalEyeKondasYojimboRedirectionEffect(), new TapSourceCost()); - ability.addTarget(new TargetSource()); - this.addAbility(ability); + this.addAbility(new SimpleActivatedAbility(Zone.BATTLEFIELD, new OpalEyeKondasYojimboRedirectionEffect(), new TapSourceCost())); // {1}{W}: Prevent the next 1 damage that would be dealt to Opal-Eye this turn. this.addAbility(new SimpleActivatedAbility(Zone.BATTLEFIELD, new PreventDamageToSourceEffect(Duration.EndOfTurn, 1), new ManaCostsImpl("{1}{W}"))); From c5399460b9e874d4b736e452f49c32f4557ba8f2 Mon Sep 17 00:00:00 2001 From: Zzooouhh Date: Wed, 18 Oct 2017 22:12:27 +0200 Subject: [PATCH 11/16] Rewrote Choose Blockers effect to ContinuousRuleModifyingEffectImpl --- Mage.Sets/src/mage/cards/m/MasterWarcraft.java | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/Mage.Sets/src/mage/cards/m/MasterWarcraft.java b/Mage.Sets/src/mage/cards/m/MasterWarcraft.java index cfbf33e8c15..2b07a65470b 100644 --- a/Mage.Sets/src/mage/cards/m/MasterWarcraft.java +++ b/Mage.Sets/src/mage/cards/m/MasterWarcraft.java @@ -34,7 +34,6 @@ import mage.abilities.common.CastOnlyDuringPhaseStepSourceAbility; import mage.abilities.condition.common.BeforeAttackersAreDeclaredCondition; import mage.abilities.effects.ContinuousRuleModifyingEffectImpl; import mage.abilities.effects.OneShotEffect; -import mage.abilities.effects.ReplacementEffectImpl; import mage.abilities.effects.RequirementEffect; import mage.abilities.effects.RestrictionEffect; import mage.abilities.effects.common.CreateDelayedTriggeredAbilityEffect; @@ -238,10 +237,10 @@ class MasterWarcraftCantAttackRestrictionEffect extends RestrictionEffect { } } -class MasterWarcraftChooseBlockersEffect extends ReplacementEffectImpl { // TODO: replace this with ContinuousRuleModifyingEffectImpl +class MasterWarcraftChooseBlockersEffect extends ContinuousRuleModifyingEffectImpl { // TODO: reverse the resolution order in case of effect multiples public MasterWarcraftChooseBlockersEffect() { - super(Duration.EndOfTurn, Outcome.Benefit); + super(Duration.EndOfTurn, Outcome.Benefit, false, false); staticText = "You choose which creatures block this turn and how those creatures block"; } @@ -266,20 +265,11 @@ class MasterWarcraftChooseBlockersEffect extends ReplacementEffectImpl { // TODO @Override public boolean applies(GameEvent event, Ability source, Game game) { - Player blockController = game.getPlayer(source.getControllerId()); - if (blockController != null) { - return true; - } - return false; - } - - @Override - public boolean replaceEvent(GameEvent event, Ability source, Game game) { Player blockController = game.getPlayer(source.getControllerId()); if (blockController != null) { game.getCombat().selectBlockers(blockController, game); return true; } return false; - } + } } From 5d126afc18df13a24d023292d238433f38c90311 Mon Sep 17 00:00:00 2001 From: Zzooouhh Date: Wed, 18 Oct 2017 22:18:32 +0200 Subject: [PATCH 12/16] Rewrote Choose Blockers effect to ContinuousRuleModifyingEffectImpl --- .../src/mage/cards/b/BrutalHordechief.java | 30 +++++++------------ 1 file changed, 11 insertions(+), 19 deletions(-) diff --git a/Mage.Sets/src/mage/cards/b/BrutalHordechief.java b/Mage.Sets/src/mage/cards/b/BrutalHordechief.java index 3311459c095..ff4b4e3cfe3 100644 --- a/Mage.Sets/src/mage/cards/b/BrutalHordechief.java +++ b/Mage.Sets/src/mage/cards/b/BrutalHordechief.java @@ -33,7 +33,7 @@ import mage.abilities.Ability; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.costs.mana.ManaCostsImpl; -import mage.abilities.effects.ReplacementEffectImpl; +import mage.abilities.effects.ContinuousRuleModifyingEffectImpl; import mage.abilities.effects.common.GainLifeEffect; import mage.abilities.effects.common.LoseLifeTargetEffect; import mage.abilities.effects.common.combat.BlocksIfAbleAllEffect; @@ -71,7 +71,7 @@ public class BrutalHordechief extends CardImpl { // {3}{R/W}{R/W}: Creatures your opponents control block this turn if able, and you choose how those creatures block. Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new BlocksIfAbleAllEffect(filter, Duration.EndOfTurn), new ManaCostsImpl("{3}{R/W}{R/W}")); - ability.addEffect(new BrutalHordechiefReplacementEffect()); + ability.addEffect(new BrutalHordechiefChooseBlockersEffect()); this.addAbility(ability); } @@ -123,20 +123,20 @@ class BrutalHordechiefTriggeredAbility extends TriggeredAbilityImpl { } } -class BrutalHordechiefReplacementEffect extends ReplacementEffectImpl { +class BrutalHordechiefChooseBlockersEffect extends ContinuousRuleModifyingEffectImpl { // TODO: reverse the resolution order in case of effect multiples - public BrutalHordechiefReplacementEffect() { - super(Duration.EndOfCombat, Outcome.Benefit); - staticText = ", and you choose how those creatures block"; + public BrutalHordechiefChooseBlockersEffect() { + super(Duration.EndOfTurn, Outcome.Benefit, false, false); + staticText = "You choose which creatures block this turn and how those creatures block"; } - public BrutalHordechiefReplacementEffect(final BrutalHordechiefReplacementEffect effect) { + public BrutalHordechiefChooseBlockersEffect(final BrutalHordechiefChooseBlockersEffect effect) { super(effect); } @Override - public BrutalHordechiefReplacementEffect copy() { - return new BrutalHordechiefReplacementEffect(this); + public BrutalHordechiefChooseBlockersEffect copy() { + return new BrutalHordechiefChooseBlockersEffect(this); } @Override @@ -153,18 +153,10 @@ class BrutalHordechiefReplacementEffect extends ReplacementEffectImpl { public boolean applies(GameEvent event, Ability source, Game game) { Player blockController = game.getPlayer(source.getControllerId()); if (blockController != null) { - return true; - } - return false; - } - - @Override - public boolean replaceEvent(GameEvent event, Ability source, Game game) { - Player blockController = game.getPlayer(source.getControllerId()); - if (blockController != null) { + game.informPlayers(source.getSourceObject(game).getIdName() + " applies"); game.getCombat().selectBlockers(blockController, game); return true; } return false; - } + } } From 12d74214ed9e6b89a52f7d040d0a70bbb9ecd896 Mon Sep 17 00:00:00 2001 From: Zzooouhh Date: Wed, 18 Oct 2017 22:20:21 +0200 Subject: [PATCH 13/16] Some stray text fixes --- Mage.Sets/src/mage/cards/b/BrutalHordechief.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Mage.Sets/src/mage/cards/b/BrutalHordechief.java b/Mage.Sets/src/mage/cards/b/BrutalHordechief.java index ff4b4e3cfe3..108280010f7 100644 --- a/Mage.Sets/src/mage/cards/b/BrutalHordechief.java +++ b/Mage.Sets/src/mage/cards/b/BrutalHordechief.java @@ -127,7 +127,7 @@ class BrutalHordechiefChooseBlockersEffect extends ContinuousRuleModifyingEffect public BrutalHordechiefChooseBlockersEffect() { super(Duration.EndOfTurn, Outcome.Benefit, false, false); - staticText = "You choose which creatures block this turn and how those creatures block"; + staticText = ", and you choose how those creatures block"; } public BrutalHordechiefChooseBlockersEffect(final BrutalHordechiefChooseBlockersEffect effect) { @@ -153,7 +153,6 @@ class BrutalHordechiefChooseBlockersEffect extends ContinuousRuleModifyingEffect public boolean applies(GameEvent event, Ability source, Game game) { Player blockController = game.getPlayer(source.getControllerId()); if (blockController != null) { - game.informPlayers(source.getSourceObject(game).getIdName() + " applies"); game.getCombat().selectBlockers(blockController, game); return true; } From 668a93d681da4ad67138c41642eb3498bf6def5b Mon Sep 17 00:00:00 2001 From: Zzooouhh Date: Thu, 19 Oct 2017 07:01:39 +0200 Subject: [PATCH 14/16] Improved targetting of MasterWarcraftAttackEffect --- Mage.Sets/src/mage/cards/m/MasterWarcraft.java | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/Mage.Sets/src/mage/cards/m/MasterWarcraft.java b/Mage.Sets/src/mage/cards/m/MasterWarcraft.java index 2b07a65470b..d16000e64ee 100644 --- a/Mage.Sets/src/mage/cards/m/MasterWarcraft.java +++ b/Mage.Sets/src/mage/cards/m/MasterWarcraft.java @@ -49,6 +49,7 @@ import mage.game.permanent.Permanent; import mage.players.Player; import mage.target.Target; import mage.target.common.TargetCreaturePermanent; +import mage.filter.predicate.permanent.ControllerPredicate; import mage.target.targetpointer.FixedTarget; /** @@ -128,6 +129,11 @@ class MasterWarcraftChooseAttackersEffect extends ContinuousRuleModifyingEffectI } class MasterWarcraftAttackEffect extends OneShotEffect { + + private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("creatures that will attack this combat (creatures not chosen won't attack this combat)"); + static { + filter.add(new ControllerPredicate(TargetController.ACTIVE)); + } MasterWarcraftAttackEffect() { super(Outcome.Benefit); @@ -146,9 +152,8 @@ class MasterWarcraftAttackEffect extends OneShotEffect { public boolean apply(Game game, Ability source) { Player controller = game.getPlayer(source.getControllerId()); if (controller != null) { - // TODO: find a way to undo creature selection - Target target = new TargetCreaturePermanent(0, Integer.MAX_VALUE, new FilterCreaturePermanent("creatures that will attack this combat (creatures not chosen won't attack this combat)"), true); - if (target.choose(Outcome.Neutral, source.getControllerId(), source.getSourceId(), game)) { + Target target = new TargetCreaturePermanent(0, Integer.MAX_VALUE, filter, true); + if (controller.chooseTarget(Outcome.Benefit, target, source, game)) { for (Permanent permanent : game.getBattlefield().getActivePermanents(new FilterCreaturePermanent(), source.getControllerId(), source.getSourceId(), game)) { // Choose creatures that will be attacking this combat @@ -237,7 +242,7 @@ class MasterWarcraftCantAttackRestrictionEffect extends RestrictionEffect { } } -class MasterWarcraftChooseBlockersEffect extends ContinuousRuleModifyingEffectImpl { // TODO: reverse the resolution order in case of effect multiples +class MasterWarcraftChooseBlockersEffect extends ContinuousRuleModifyingEffectImpl { // TODO: fix sorting order in case of Master Warcraft multiples public MasterWarcraftChooseBlockersEffect() { super(Duration.EndOfTurn, Outcome.Benefit, false, false); From 3558899033cf5c97f773b6f1b8fdc08fc7572742 Mon Sep 17 00:00:00 2001 From: Zzooouhh Date: Fri, 20 Oct 2017 00:41:32 +0200 Subject: [PATCH 15/16] Major code revamp, created a new watcher --- .../src/mage/cards/m/MasterWarcraft.java | 175 ++++++++++-------- 1 file changed, 102 insertions(+), 73 deletions(-) diff --git a/Mage.Sets/src/mage/cards/m/MasterWarcraft.java b/Mage.Sets/src/mage/cards/m/MasterWarcraft.java index d16000e64ee..23c5a7a503a 100644 --- a/Mage.Sets/src/mage/cards/m/MasterWarcraft.java +++ b/Mage.Sets/src/mage/cards/m/MasterWarcraft.java @@ -28,6 +28,7 @@ package mage.cards.m; import java.util.*; +import mage.MageObject; import mage.abilities.Ability; import mage.abilities.Mode; import mage.abilities.common.CastOnlyDuringPhaseStepSourceAbility; @@ -45,12 +46,15 @@ import mage.filter.common.FilterCreaturePermanent; import mage.game.Game; import mage.game.events.GameEvent; import mage.game.events.GameEvent.EventType; +import mage.game.events.ZoneChangeEvent; import mage.game.permanent.Permanent; +import mage.game.stack.Spell; import mage.players.Player; import mage.target.Target; import mage.target.common.TargetCreaturePermanent; import mage.filter.predicate.permanent.ControllerPredicate; import mage.target.targetpointer.FixedTarget; +import mage.watchers.Watcher; /** * @@ -62,13 +66,18 @@ public class MasterWarcraft extends CardImpl { super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{2}{R/W}{R/W}"); // Cast Master Warcraft only before attackers are declared. - this.addAbility(new CastOnlyDuringPhaseStepSourceAbility(null, null, BeforeAttackersAreDeclaredCondition.instance)); + this.addAbility(new CastOnlyDuringPhaseStepSourceAbility(null, null, BeforeAttackersAreDeclaredCondition.instance, "Cast Master Warcraft only before attackers are declared")); // You choose which creatures attack this turn. this.getSpellAbility().addEffect(new MasterWarcraftChooseAttackersEffect()); // You choose which creatures block this turn and how those creatures block. this.getSpellAbility().addEffect(new MasterWarcraftChooseBlockersEffect()); + + + // (only the last resolved Master Warcraft spell's effects apply) + this.getSpellAbility().addWatcher(new MasterWarcraftCastWatcher()); + this.getSpellAbility().addEffect(new MasterWarcraftCastWatcherIncrementEffect()); } public MasterWarcraft(final MasterWarcraft card) { @@ -83,6 +92,11 @@ public class MasterWarcraft extends CardImpl { class MasterWarcraftChooseAttackersEffect extends ContinuousRuleModifyingEffectImpl { + private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("creatures that will attack this combat (creatures not chosen won't attack this combat)"); + static { + filter.add(new ControllerPredicate(TargetController.ACTIVE)); + } + public MasterWarcraftChooseAttackersEffect() { super(Duration.EndOfTurn, Outcome.Benefit, false, false); staticText = "You choose which creatures attack this turn"; @@ -109,61 +123,27 @@ class MasterWarcraftChooseAttackersEffect extends ContinuousRuleModifyingEffectI @Override public boolean applies(GameEvent event, Ability source, Game game) { - Player chooser = game.getPlayer(source.getControllerId()); - Player attackingPlayer = game.getPlayer(game.getCombat().getAttackingPlayerId()); - if (chooser != null && attackingPlayer != null && !attackingPlayer.getAvailableAttackers(game).isEmpty()) { - for (Permanent permanent : game.getBattlefield().getActivePermanents(new FilterCreaturePermanent(), source.getControllerId(), source.getSourceId(), game)) { - // Clears previous instances of "should attack" effects - // ("shouldn't attack" effects don't need cleaning because MasterWarcraftMustAttackRequirementEffect overrides them) - for (Map.Entry> entry : game.getContinuousEffects().getApplicableRequirementEffects(permanent, false, game).entrySet()) { - RequirementEffect effect = entry.getKey(); - if (effect instanceof MasterWarcraftMustAttackRequirementEffect) { - effect.discard(); - } - } - } - new MasterWarcraftAttackEffect().apply(game, source); // Master Warcraft imposes its effect right before the attackers being declared... + MasterWarcraftCastWatcher watcher = (MasterWarcraftCastWatcher) game.getState().getWatchers().get(MasterWarcraftCastWatcher.class.getSimpleName()); + watcher.decrement(); + if (watcher.copyCountApply > 0) { + game.informPlayers(source.getSourceObject(game).getIdName() + " didn't apply"); + return false; } - return false; // ...and then resumes the attack declaration for the active player as normal - } -} - -class MasterWarcraftAttackEffect extends OneShotEffect { - - private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("creatures that will attack this combat (creatures not chosen won't attack this combat)"); - static { - filter.add(new ControllerPredicate(TargetController.ACTIVE)); - } - - MasterWarcraftAttackEffect() { - super(Outcome.Benefit); - } - - MasterWarcraftAttackEffect(final MasterWarcraftAttackEffect effect) { - super(effect); - } - - @Override - public MasterWarcraftAttackEffect copy() { - return new MasterWarcraftAttackEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { + watcher.copyCountApply = watcher.copyCount; Player controller = game.getPlayer(source.getControllerId()); - if (controller != null) { + Player attackingPlayer = game.getPlayer(game.getCombat().getAttackingPlayerId()); + if (controller != null && attackingPlayer != null && !attackingPlayer.getAvailableAttackers(game).isEmpty()) { Target target = new TargetCreaturePermanent(0, Integer.MAX_VALUE, filter, true); if (controller.chooseTarget(Outcome.Benefit, target, source, game)) { for (Permanent permanent : game.getBattlefield().getActivePermanents(new FilterCreaturePermanent(), source.getControllerId(), source.getSourceId(), game)) { // Choose creatures that will be attacking this combat if (target.getTargets().contains(permanent.getId())) { - RequirementEffect effect = new MasterWarcraftMustAttackRequirementEffect(); + RequirementEffect effect = new AttacksIfAbleTargetEffect(Duration.EndOfCombat); effect.setText(""); effect.setTargetPointer(new FixedTarget(permanent.getId())); game.addEffect(effect, source); - // TODO: find a better way to report attackers to game log - // game.informPlayers(controller.getLogName() + " has decided that " + permanent.getLogName() + " should attack this combat if able"); + game.informPlayers(controller.getLogName() + " has decided that " + permanent.getLogName() + " attacks this combat if able"); // All other creatures can't attack } else { @@ -172,36 +152,10 @@ class MasterWarcraftAttackEffect extends OneShotEffect { effect.setTargetPointer(new FixedTarget(permanent.getId())); game.addEffect(effect, source); } - } - return true; } } - return false; - } -} - -class MasterWarcraftMustAttackRequirementEffect extends AttacksIfAbleTargetEffect { - - MasterWarcraftMustAttackRequirementEffect() { - super(Duration.EndOfCombat); - } - - MasterWarcraftMustAttackRequirementEffect(final MasterWarcraftMustAttackRequirementEffect effect) { - super(effect); - } - - @Override - public MasterWarcraftMustAttackRequirementEffect copy() { - return new MasterWarcraftMustAttackRequirementEffect(this); - } - - @Override - public boolean applies(Permanent permanent, Ability source, Game game) { - if (discarded) { - return false; - } - return this.getTargetPointer().getTargets(game, source).contains(permanent.getId()); + return false; // the attack declaration resumes for the active player as normal } } @@ -242,7 +196,7 @@ class MasterWarcraftCantAttackRestrictionEffect extends RestrictionEffect { } } -class MasterWarcraftChooseBlockersEffect extends ContinuousRuleModifyingEffectImpl { // TODO: fix sorting order in case of Master Warcraft multiples +class MasterWarcraftChooseBlockersEffect extends ContinuousRuleModifyingEffectImpl { public MasterWarcraftChooseBlockersEffect() { super(Duration.EndOfTurn, Outcome.Benefit, false, false); @@ -270,6 +224,13 @@ class MasterWarcraftChooseBlockersEffect extends ContinuousRuleModifyingEffectIm @Override public boolean applies(GameEvent event, Ability source, Game game) { + MasterWarcraftCastWatcher watcher = (MasterWarcraftCastWatcher) game.getState().getWatchers().get(MasterWarcraftCastWatcher.class.getSimpleName()); + watcher.decrement(); + if (watcher.copyCountApply > 0) { + game.informPlayers(source.getSourceObject(game).getIdName() + " didn't apply"); + return false; + } + watcher.copyCountApply = watcher.copyCount; Player blockController = game.getPlayer(source.getControllerId()); if (blockController != null) { game.getCombat().selectBlockers(blockController, game); @@ -278,3 +239,71 @@ class MasterWarcraftChooseBlockersEffect extends ContinuousRuleModifyingEffectIm return false; } } + +class MasterWarcraftCastWatcher extends Watcher { + + public int copyCount = 0; + public int copyCountApply = 0; + + public MasterWarcraftCastWatcher() { + super(MasterWarcraftCastWatcher.class.getSimpleName(), WatcherScope.GAME); + } + + public MasterWarcraftCastWatcher(final MasterWarcraftCastWatcher watcher) { + super(watcher); + this.copyCount = watcher.copyCount; + this.copyCountApply = watcher.copyCountApply; + } + + @Override + public void reset() { + copyCount = 0; + copyCountApply = 0; + } + + @Override + public MasterWarcraftCastWatcher copy() { + return new MasterWarcraftCastWatcher(this); + } + + @Override + public void watch(GameEvent event, Game game) { + } + + public void increment() { + copyCount++; + copyCountApply = copyCount; + } + + public void decrement() { + if (copyCountApply > 0); { + copyCountApply--; + } + } +} + +class MasterWarcraftCastWatcherIncrementEffect extends OneShotEffect { + + MasterWarcraftCastWatcherIncrementEffect() { + super(Outcome.Neutral); + } + + MasterWarcraftCastWatcherIncrementEffect(final MasterWarcraftCastWatcherIncrementEffect effect) { + super(effect); + } + + @Override + public boolean apply(Game game, Ability source) { + MasterWarcraftCastWatcher watcher = (MasterWarcraftCastWatcher) game.getState().getWatchers().get(MasterWarcraftCastWatcher.class.getSimpleName()); + if (watcher != null) { + watcher.increment(); + return true; + } + return false; + } + + @Override + public MasterWarcraftCastWatcherIncrementEffect copy() { + return new MasterWarcraftCastWatcherIncrementEffect(this); + } +} From 483e6549f6bf8b6acd3bee1cc9f77a12405372bb Mon Sep 17 00:00:00 2001 From: Zzooouhh Date: Fri, 20 Oct 2017 02:01:09 +0200 Subject: [PATCH 16/16] Cleaned up code, fixed NullPointerException --- .../src/mage/cards/m/MasterWarcraft.java | 57 +++++-------------- 1 file changed, 15 insertions(+), 42 deletions(-) diff --git a/Mage.Sets/src/mage/cards/m/MasterWarcraft.java b/Mage.Sets/src/mage/cards/m/MasterWarcraft.java index 23c5a7a503a..7bac5394538 100644 --- a/Mage.Sets/src/mage/cards/m/MasterWarcraft.java +++ b/Mage.Sets/src/mage/cards/m/MasterWarcraft.java @@ -39,6 +39,7 @@ import mage.abilities.effects.RequirementEffect; import mage.abilities.effects.RestrictionEffect; import mage.abilities.effects.common.CreateDelayedTriggeredAbilityEffect; import mage.abilities.effects.common.combat.AttacksIfAbleTargetEffect; +import mage.abilities.effects.common.combat.CantAttackTargetEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.*; @@ -145,12 +146,21 @@ class MasterWarcraftChooseAttackersEffect extends ContinuousRuleModifyingEffectI game.addEffect(effect, source); game.informPlayers(controller.getLogName() + " has decided that " + permanent.getLogName() + " attacks this combat if able"); - // All other creatures can't attack + // All other creatures can't attack (unless they must attack) } else { - RestrictionEffect effect = new MasterWarcraftCantAttackRestrictionEffect(); - effect.setText(""); - effect.setTargetPointer(new FixedTarget(permanent.getId())); - game.addEffect(effect, source); + boolean hasToAttack = false; + for (Map.Entry> entry : game.getContinuousEffects().getApplicableRequirementEffects(permanent, false, game).entrySet()) { + RequirementEffect effect2 = entry.getKey(); + if (effect2.mustAttack(game)) { + hasToAttack = true; + } + } + if (!hasToAttack) { + RestrictionEffect effect = new CantAttackTargetEffect(Duration.EndOfCombat); + effect.setText(""); + effect.setTargetPointer(new FixedTarget(permanent.getId())); + game.addEffect(effect, source); + } } } } @@ -159,43 +169,6 @@ class MasterWarcraftChooseAttackersEffect extends ContinuousRuleModifyingEffectI } } -class MasterWarcraftCantAttackRestrictionEffect extends RestrictionEffect { - - MasterWarcraftCantAttackRestrictionEffect() { - super(Duration.EndOfCombat); - } - - MasterWarcraftCantAttackRestrictionEffect(final MasterWarcraftCantAttackRestrictionEffect effect) { - super(effect); - } - - @Override - public MasterWarcraftCantAttackRestrictionEffect copy() { - return new MasterWarcraftCantAttackRestrictionEffect(this); - } - - @Override - public boolean applies(Permanent permanent, Ability source, Game game) { - return this.getTargetPointer().getFirst(game, source).equals(permanent.getId()); - } - - @Override - public boolean canAttack(Permanent attacker, UUID defenderId, Ability source, Game game) { - for (Map.Entry> entry : game.getContinuousEffects().getApplicableRequirementEffects(attacker, false, game).entrySet()) { - RequirementEffect effect = entry.getKey(); - if (effect.mustAttack(game)) { - return true; - } - } - return false; - } - - @Override - public String getText(Mode mode) { - return "Unless {this} must attack, {this} can't attack."; - } -} - class MasterWarcraftChooseBlockersEffect extends ContinuousRuleModifyingEffectImpl { public MasterWarcraftChooseBlockersEffect() {