From f7e821be2f35dcde1fb65bfea8499c0c344d1dd6 Mon Sep 17 00:00:00 2001 From: Evan Kranzler Date: Sun, 10 Oct 2021 10:25:10 -0400 Subject: [PATCH] Reworking effects that allow controlling combat (WIP) (#8159) * reworked effects that allow controlling combat * [AFC] Implemented Berserker's Frenzy * [AFC] updated Berserker's Frenzy to roll correctly --- .../src/mage/cards/b/BerserkersFrenzy.java | 107 ++++++++ .../src/mage/cards/b/BrutalHordechief.java | 99 +------- .../src/mage/cards/d/DivinersPortent.java | 2 +- .../src/mage/cards/m/MasterWarcraft.java | 239 ++++-------------- Mage.Sets/src/mage/cards/m/Melee.java | 106 ++------ .../mage/cards/o/OdricMasterTactician.java | 91 +------ Mage.Sets/src/mage/cards/r/Revivify.java | 2 +- .../mage/sets/ForgottenRealmsCommander.java | 1 + .../common/RollDieWithResultTableEffect.java | 11 +- .../common/combat/ChooseBlockersEffect.java | 80 ++++++ .../ChooseBlockersRedundancyWatcher.java | 42 --- .../ControlCombatRedundancyWatcher.java | 78 ++++++ 12 files changed, 366 insertions(+), 492 deletions(-) create mode 100644 Mage.Sets/src/mage/cards/b/BerserkersFrenzy.java create mode 100644 Mage/src/main/java/mage/abilities/effects/common/combat/ChooseBlockersEffect.java delete mode 100644 Mage/src/main/java/mage/watchers/common/ChooseBlockersRedundancyWatcher.java create mode 100644 Mage/src/main/java/mage/watchers/common/ControlCombatRedundancyWatcher.java diff --git a/Mage.Sets/src/mage/cards/b/BerserkersFrenzy.java b/Mage.Sets/src/mage/cards/b/BerserkersFrenzy.java new file mode 100644 index 00000000000..2dc7617417e --- /dev/null +++ b/Mage.Sets/src/mage/cards/b/BerserkersFrenzy.java @@ -0,0 +1,107 @@ +package mage.cards.b; + +import mage.abilities.Ability; +import mage.abilities.common.CastOnlyDuringPhaseStepSourceAbility; +import mage.abilities.condition.Condition; +import mage.abilities.dynamicvalue.common.StaticValue; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.RollDieWithResultTableEffect; +import mage.abilities.effects.common.combat.BlocksIfAbleTargetEffect; +import mage.abilities.effects.common.combat.ChooseBlockersEffect; +import mage.abilities.hint.ConditionHint; +import mage.abilities.hint.Hint; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.cards.CardsImpl; +import mage.constants.*; +import mage.game.Game; +import mage.players.Player; +import mage.target.TargetPermanent; +import mage.target.common.TargetCreaturePermanent; +import mage.target.targetpointer.FixedTargets; +import mage.watchers.common.ControlCombatRedundancyWatcher; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class BerserkersFrenzy extends CardImpl { + + private static final Hint hint = new ConditionHint(BerserkersFrenzyCondition.instance, "Can be cast"); + + public BerserkersFrenzy(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{2}{R}"); + + // Cast this spell only before combat or during combat before blockers are declared. + this.addAbility(new CastOnlyDuringPhaseStepSourceAbility( + null, null, BerserkersFrenzyCondition.instance, + "Cast this spell only before combat or during combat before blockers are declared" + ).addHint(hint)); + + // Roll two d20 and ignore the lower roll. + RollDieWithResultTableEffect effect = new RollDieWithResultTableEffect( + 20, "roll two d20 and ignore the lower roll", StaticValue.get(0), 1 + ); + + // 1-14 | Choose any number of creatures. They block this turn if able. + effect.addTableEntry(1, 14, new BerserkersFrenzyEffect()); + + // 15-20 | You choose which creatures block this turn and how those creatures block. + effect.addTableEntry(15, 20, new ChooseBlockersEffect(Duration.EndOfTurn)); + this.getSpellAbility().addEffect(effect); + this.getSpellAbility().addWatcher(new ControlCombatRedundancyWatcher()); + } + + private BerserkersFrenzy(final BerserkersFrenzy card) { + super(card); + } + + @Override + public BerserkersFrenzy copy() { + return new BerserkersFrenzy(this); + } +} + +enum BerserkersFrenzyCondition implements Condition { + instance; + + @Override + public boolean apply(Game game, Ability source) { + if (game.getPhase().getType() == TurnPhase.COMBAT) { + return game.getStep().getType().isBefore(PhaseStep.DECLARE_BLOCKERS); + } + return !game.getTurn().isDeclareAttackersStepStarted(); + } +} + +class BerserkersFrenzyEffect extends OneShotEffect { + + BerserkersFrenzyEffect() { + super(Outcome.Benefit); + staticText = "choose any number of creatures. They block this turn if able"; + } + + private BerserkersFrenzyEffect(final BerserkersFrenzyEffect effect) { + super(effect); + } + + @Override + public BerserkersFrenzyEffect copy() { + return new BerserkersFrenzyEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + if (player == null) { + return false; + } + TargetPermanent target = new TargetCreaturePermanent(0, Integer.MAX_VALUE); + target.setNotTarget(true); + player.choose(outcome, target, source.getSourceId(), game); + game.addEffect(new BlocksIfAbleTargetEffect(Duration.EndOfTurn) + .setTargetPointer(new FixedTargets(new CardsImpl(target.getTargets()), game)), source); + return true; + } +} diff --git a/Mage.Sets/src/mage/cards/b/BrutalHordechief.java b/Mage.Sets/src/mage/cards/b/BrutalHordechief.java index 2cf83cfb40b..8b0eb470a61 100644 --- a/Mage.Sets/src/mage/cards/b/BrutalHordechief.java +++ b/Mage.Sets/src/mage/cards/b/BrutalHordechief.java @@ -1,17 +1,14 @@ - package mage.cards.b; -import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.costs.mana.ManaCostsImpl; -import mage.abilities.effects.ContinuousRuleModifyingEffectImpl; -import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.GainLifeEffect; import mage.abilities.effects.common.LoseLifeTargetEffect; import mage.abilities.effects.common.combat.BlocksIfAbleAllEffect; +import mage.abilities.effects.common.combat.ChooseBlockersEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.*; @@ -19,12 +16,12 @@ import mage.filter.common.FilterCreaturePermanent; import mage.game.Game; import mage.game.events.GameEvent; import mage.game.permanent.Permanent; -import mage.players.Player; import mage.target.targetpointer.FixedTarget; -import mage.watchers.common.ChooseBlockersRedundancyWatcher; +import mage.watchers.common.ControlCombatRedundancyWatcher; + +import java.util.UUID; /** - * * @author LevelX2 */ public final class BrutalHordechief extends CardImpl { @@ -36,7 +33,7 @@ public final class BrutalHordechief extends CardImpl { } public BrutalHordechief(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{3}{B}"); + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{B}"); this.subtype.add(SubType.ORC, SubType.WARRIOR); this.power = new MageInt(3); this.toughness = new MageInt(3); @@ -45,10 +42,11 @@ public final class BrutalHordechief extends CardImpl { this.addAbility(new BrutalHordechiefTriggeredAbility()); // {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 BrutalHordechiefChooseBlockersEffect()); - ability.addWatcher(new ChooseBlockersRedundancyWatcher()); - ability.addEffect(new ChooseBlockersRedundancyWatcherIncrementEffect()); + Ability ability = new SimpleActivatedAbility( + new BlocksIfAbleAllEffect(filter, Duration.EndOfTurn), new ManaCostsImpl<>("{3}{R/W}{R/W}") + ); + ability.addEffect(new ChooseBlockersEffect(Duration.EndOfTurn).setText("and you choose how those creatures block")); + ability.addWatcher(new ControlCombatRedundancyWatcher()); this.addAbility(ability); } @@ -60,32 +58,6 @@ public final class BrutalHordechief extends CardImpl { public BrutalHordechief copy() { return new BrutalHordechief(this); } - - private class ChooseBlockersRedundancyWatcherIncrementEffect extends OneShotEffect { - - ChooseBlockersRedundancyWatcherIncrementEffect() { - super(Outcome.Neutral); - } - - ChooseBlockersRedundancyWatcherIncrementEffect(final ChooseBlockersRedundancyWatcherIncrementEffect effect) { - super(effect); - } - - @Override - public boolean apply(Game game, Ability source) { - ChooseBlockersRedundancyWatcher watcher = game.getState().getWatcher(ChooseBlockersRedundancyWatcher.class); - if (watcher != null) { - watcher.increment(); - return true; - } - return false; - } - - @Override - public ChooseBlockersRedundancyWatcherIncrementEffect copy() { - return new ChooseBlockersRedundancyWatcherIncrementEffect(this); - } - } } class BrutalHordechiefTriggeredAbility extends TriggeredAbilityImpl { @@ -112,9 +84,9 @@ class BrutalHordechiefTriggeredAbility extends TriggeredAbilityImpl { @Override public boolean checkTrigger(GameEvent event, Game game) { Permanent source = game.getPermanent(event.getSourceId()); - if (source != null && source.isControlledBy(controllerId)) { + if (source != null && source.isControlledBy(getControllerId())) { UUID defendingPlayerId = game.getCombat().getDefendingPlayerId(event.getSourceId(), game); - this.getEffects().get(0).setTargetPointer(new FixedTarget(defendingPlayerId)); + this.getEffects().setTargetPointer(new FixedTarget(defendingPlayerId)); return true; } return false; @@ -125,50 +97,3 @@ class BrutalHordechiefTriggeredAbility extends TriggeredAbilityImpl { return "Whenever a creature you control attacks, defending player loses 1 life and you gain 1 life."; } } - -class BrutalHordechiefChooseBlockersEffect extends ContinuousRuleModifyingEffectImpl { - - public BrutalHordechiefChooseBlockersEffect() { - super(Duration.EndOfTurn, Outcome.Benefit, false, false); - staticText = "You choose which creatures block this turn and how those creatures block"; - } - - public BrutalHordechiefChooseBlockersEffect(final BrutalHordechiefChooseBlockersEffect effect) { - super(effect); - } - - @Override - public BrutalHordechiefChooseBlockersEffect copy() { - return new BrutalHordechiefChooseBlockersEffect(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) { - ChooseBlockersRedundancyWatcher watcher = game.getState().getWatcher(ChooseBlockersRedundancyWatcher.class); - if(watcher == null){ - return false; - } - 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, source, game); - return true; - } - return false; - } -} diff --git a/Mage.Sets/src/mage/cards/d/DivinersPortent.java b/Mage.Sets/src/mage/cards/d/DivinersPortent.java index c127f3b36b0..cb8f5a731dc 100644 --- a/Mage.Sets/src/mage/cards/d/DivinersPortent.java +++ b/Mage.Sets/src/mage/cards/d/DivinersPortent.java @@ -26,7 +26,7 @@ public final class DivinersPortent extends CardImpl { // Roll a d20 and add the number of cards in your hand. RollDieWithResultTableEffect effect = new RollDieWithResultTableEffect( 20, "roll a d20 and add the number " + - "of cards in your hand", CardsInControllerHandCount.instance + "of cards in your hand", CardsInControllerHandCount.instance, 0 ); this.getSpellAbility().addEffect(effect); diff --git a/Mage.Sets/src/mage/cards/m/MasterWarcraft.java b/Mage.Sets/src/mage/cards/m/MasterWarcraft.java index e739dbb994f..f8451295410 100644 --- a/Mage.Sets/src/mage/cards/m/MasterWarcraft.java +++ b/Mage.Sets/src/mage/cards/m/MasterWarcraft.java @@ -1,20 +1,20 @@ - package mage.cards.m; -import java.util.*; - import mage.abilities.Ability; 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.RequirementEffect; import mage.abilities.effects.RestrictionEffect; import mage.abilities.effects.common.combat.AttacksIfAbleTargetEffect; import mage.abilities.effects.common.combat.CantAttackTargetEffect; +import mage.abilities.effects.common.combat.ChooseBlockersEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.constants.*; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.Outcome; +import mage.constants.TargetController; import mage.filter.common.FilterCreaturePermanent; import mage.game.Game; import mage.game.events.GameEvent; @@ -23,11 +23,13 @@ import mage.players.Player; import mage.target.Target; import mage.target.common.TargetCreaturePermanent; import mage.target.targetpointer.FixedTarget; -import mage.watchers.Watcher; -import mage.watchers.common.ChooseBlockersRedundancyWatcher; +import mage.watchers.common.ControlCombatRedundancyWatcher; + +import java.util.Map; +import java.util.Set; +import java.util.UUID; /** - * * @author L_J */ public final class MasterWarcraft extends CardImpl { @@ -36,20 +38,19 @@ public final 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, "Cast this spell only before attackers are declared")); + this.addAbility(new CastOnlyDuringPhaseStepSourceAbility( + null, null, BeforeAttackersAreDeclaredCondition.instance, + "Cast this spell 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()); - + this.getSpellAbility().addEffect(new ChooseBlockersEffect(Duration.EndOfTurn).concatBy("
")); // (only the last resolved Master Warcraft spell's effects apply) - this.getSpellAbility().addWatcher(new MasterWarcraftCastWatcher()); - this.getSpellAbility().addEffect(new MasterWarcraftCastWatcherIncrementEffect()); - this.getSpellAbility().addWatcher(new ChooseBlockersRedundancyWatcher()); - this.getSpellAbility().addEffect(new ChooseBlockersRedundancyWatcherIncrementEffect()); + this.getSpellAbility().addWatcher(new ControlCombatRedundancyWatcher()); } private MasterWarcraft(final MasterWarcraft card) { @@ -60,73 +61,22 @@ public final class MasterWarcraft extends CardImpl { public MasterWarcraft copy() { return new MasterWarcraft(this); } - - private class MasterWarcraftCastWatcherIncrementEffect extends OneShotEffect { - - MasterWarcraftCastWatcherIncrementEffect() { - super(Outcome.Neutral); - } - - MasterWarcraftCastWatcherIncrementEffect(final MasterWarcraftCastWatcherIncrementEffect effect) { - super(effect); - } - - @Override - public boolean apply(Game game, Ability source) { - MasterWarcraftCastWatcher watcher = game.getState().getWatcher(MasterWarcraftCastWatcher.class); - if (watcher != null) { - watcher.increment(); - return true; - } - return false; - } - - @Override - public MasterWarcraftCastWatcherIncrementEffect copy() { - return new MasterWarcraftCastWatcherIncrementEffect(this); - } - } - - private class ChooseBlockersRedundancyWatcherIncrementEffect extends OneShotEffect { - - ChooseBlockersRedundancyWatcherIncrementEffect() { - super(Outcome.Neutral); - } - - ChooseBlockersRedundancyWatcherIncrementEffect(final ChooseBlockersRedundancyWatcherIncrementEffect effect) { - super(effect); - } - - @Override - public boolean apply(Game game, Ability source) { - ChooseBlockersRedundancyWatcher watcher = game.getState().getWatcher(ChooseBlockersRedundancyWatcher.class); - if (watcher != null) { - watcher.increment(); - return true; - } - return false; - } - - @Override - public ChooseBlockersRedundancyWatcherIncrementEffect copy() { - return new ChooseBlockersRedundancyWatcherIncrementEffect(this); - } - } } 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(TargetController.ACTIVE.getControllerPredicate()); } - public MasterWarcraftChooseAttackersEffect() { + MasterWarcraftChooseAttackersEffect() { super(Duration.EndOfTurn, Outcome.Benefit, false, false); staticText = "You choose which creatures attack this turn"; } - public MasterWarcraftChooseAttackersEffect(final MasterWarcraftChooseAttackersEffect effect) { + private MasterWarcraftChooseAttackersEffect(final MasterWarcraftChooseAttackersEffect effect) { super(effect); } @@ -145,131 +95,54 @@ class MasterWarcraftChooseAttackersEffect extends ContinuousRuleModifyingEffectI return event.getType() == GameEvent.EventType.DECLARING_ATTACKERS; } + @Override + public void init(Ability source, Game game) { + super.init(source, game); + ControlCombatRedundancyWatcher.addAttackingController(source.getControllerId(), duration, game); + } + @Override public boolean applies(GameEvent event, Ability source, Game game) { - MasterWarcraftCastWatcher watcher = game.getState().getWatcher(MasterWarcraftCastWatcher.class); - if(watcher == null){ - return false; - } - watcher.decrement(); - if (watcher.copyCountApply > 0) { + if (!ControlCombatRedundancyWatcher.checkAttackingController(source.getControllerId(), game)) { game.informPlayers(source.getSourceObject(game).getIdName() + " didn't apply"); return false; } - watcher.copyCountApply = watcher.copyCount; Player controller = game.getPlayer(source.getControllerId()); 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 AttacksIfAbleTargetEffect(Duration.EndOfCombat); - effect.setText(""); - effect.setTargetPointer(new FixedTarget(permanent, game)); - game.addEffect(effect, source); - game.informPlayers(controller.getLogName() + " has decided that " + permanent.getLogName() + " attacks this combat if able"); - - // All other creatures can't attack (unless they must attack) - } else { - 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, game)); - game.addEffect(effect, source); - } + if (controller == null || attackingPlayer == null || attackingPlayer.getAvailableAttackers(game).isEmpty()) { + return false; // the attack declaration resumes for the active player as normal + } + Target target = new TargetCreaturePermanent(0, Integer.MAX_VALUE, filter, true); + if (!controller.chooseTarget(Outcome.Benefit, target, source, game)) { + return false; // the attack declaration resumes for the active player as normal + } + 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); + effect.setText(""); + effect.setTargetPointer(new FixedTarget(permanent, game)); + game.addEffect(effect, source); + game.informPlayers(controller.getLogName() + " has decided that " + permanent.getLogName() + " attacks this combat if able"); + + // All other creatures can't attack (unless they must attack) + } else { + 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, game)); + game.addEffect(effect, source); + } } } return false; // the attack declaration resumes for the active player as normal } } - -class MasterWarcraftChooseBlockersEffect extends ContinuousRuleModifyingEffectImpl { - - public MasterWarcraftChooseBlockersEffect() { - super(Duration.EndOfTurn, Outcome.Benefit, false, false); - 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) { - ChooseBlockersRedundancyWatcher watcher = game.getState().getWatcher(ChooseBlockersRedundancyWatcher.class); - if(watcher == null){ - return false; - } - 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, source, game); - return true; - } - return false; - } -} - -class MasterWarcraftCastWatcher extends Watcher { - - public int copyCount = 0; - public int copyCountApply = 0; - - public MasterWarcraftCastWatcher() { - super(WatcherScope.GAME); - } - - - @Override - public void reset() { - copyCount = 0; - copyCountApply = 0; - } - - @Override - public void watch(GameEvent event, Game game) { - } - - public void increment() { - copyCount++; - copyCountApply = copyCount; - } - - public void decrement() { - if (copyCountApply > 0) { - copyCountApply--; - } - } -} diff --git a/Mage.Sets/src/mage/cards/m/Melee.java b/Mage.Sets/src/mage/cards/m/Melee.java index 1d9cca9086f..3ff9968ce8c 100644 --- a/Mage.Sets/src/mage/cards/m/Melee.java +++ b/Mage.Sets/src/mage/cards/m/Melee.java @@ -1,6 +1,5 @@ package mage.cards.m; -import mage.abilities.Ability; import mage.abilities.DelayedTriggeredAbility; import mage.abilities.common.CastOnlyDuringPhaseStepSourceAbility; import mage.abilities.condition.CompoundCondition; @@ -8,25 +7,23 @@ import mage.abilities.condition.Condition; import mage.abilities.condition.common.BeforeBlockersAreDeclaredCondition; import mage.abilities.condition.common.IsPhaseCondition; import mage.abilities.condition.common.MyTurnCondition; -import mage.abilities.effects.ContinuousRuleModifyingEffectImpl; -import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.CreateDelayedTriggeredAbilityEffect; import mage.abilities.effects.common.RemoveFromCombatTargetEffect; import mage.abilities.effects.common.UntapTargetEffect; +import mage.abilities.effects.common.combat.ChooseBlockersEffect; import mage.abilities.hint.ConditionHint; +import mage.abilities.hint.Hint; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.Duration; -import mage.constants.Outcome; import mage.constants.TurnPhase; import mage.game.Game; import mage.game.combat.CombatGroup; import mage.game.events.GameEvent; import mage.game.permanent.Permanent; -import mage.players.Player; import mage.target.targetpointer.FixedTarget; -import mage.watchers.common.ChooseBlockersRedundancyWatcher; +import mage.watchers.common.ControlCombatRedundancyWatcher; import java.util.UUID; @@ -35,21 +32,26 @@ import java.util.UUID; */ public final class Melee extends CardImpl { + private static final Condition condition = new CompoundCondition( + BeforeBlockersAreDeclaredCondition.instance, + new IsPhaseCondition(TurnPhase.COMBAT), + MyTurnCondition.instance + ); + private static final Hint hint = new ConditionHint(condition, "Can be cast"); + public Melee(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{4}{R}"); // Cast Melee only during your turn and only during combat before blockers are declared. - Condition condition = new CompoundCondition(BeforeBlockersAreDeclaredCondition.instance, - new IsPhaseCondition(TurnPhase.COMBAT), - MyTurnCondition.instance); - this.addAbility(new CastOnlyDuringPhaseStepSourceAbility(null, null, condition, "Cast this spell only during your turn and only during combat before blockers are declared") - .addHint(new ConditionHint(condition, "Can cast melee (it's combat phase on your turn)"))); + this.addAbility(new CastOnlyDuringPhaseStepSourceAbility( + null, null, condition, + "Cast this spell only during your turn and only during combat before blockers are declared" + ).addHint(hint)); // You choose which creatures block this combat and how those creatures block. // (only the last resolved Melee spell's blocking effect applies) - this.getSpellAbility().addEffect(new MeleeChooseBlockersEffect()); - this.getSpellAbility().addWatcher(new ChooseBlockersRedundancyWatcher()); - this.getSpellAbility().addEffect(new ChooseBlockersRedundancyWatcherIncrementEffect()); + this.getSpellAbility().addEffect(new ChooseBlockersEffect(Duration.EndOfCombat)); + this.getSpellAbility().addWatcher(new ControlCombatRedundancyWatcher()); // Whenever a creature attacks and isn't blocked this combat, untap it and remove it from combat. this.getSpellAbility().addEffect(new CreateDelayedTriggeredAbilityEffect(new MeleeTriggeredAbility())); @@ -63,82 +65,6 @@ public final class Melee extends CardImpl { public Melee copy() { return new Melee(this); } - - private class ChooseBlockersRedundancyWatcherIncrementEffect extends OneShotEffect { - - ChooseBlockersRedundancyWatcherIncrementEffect() { - super(Outcome.Neutral); - } - - ChooseBlockersRedundancyWatcherIncrementEffect(final ChooseBlockersRedundancyWatcherIncrementEffect effect) { - super(effect); - } - - @Override - public boolean apply(Game game, Ability source) { - ChooseBlockersRedundancyWatcher watcher = game.getState().getWatcher(ChooseBlockersRedundancyWatcher.class); - if (watcher != null) { - watcher.increment(); - return true; - } - return false; - } - - @Override - public ChooseBlockersRedundancyWatcherIncrementEffect copy() { - return new ChooseBlockersRedundancyWatcherIncrementEffect(this); - } - } -} - -class MeleeChooseBlockersEffect extends ContinuousRuleModifyingEffectImpl { - - public MeleeChooseBlockersEffect() { - super(Duration.EndOfCombat, Outcome.Benefit, false, false); - staticText = "You choose which creatures block this combat and how those creatures block"; - } - - public MeleeChooseBlockersEffect(final MeleeChooseBlockersEffect effect) { - super(effect); - } - - @Override - public MeleeChooseBlockersEffect copy() { - return new MeleeChooseBlockersEffect(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) { - ChooseBlockersRedundancyWatcher watcher = game.getState().getWatcher(ChooseBlockersRedundancyWatcher.class); - if (watcher == null) { - return false; - } - watcher.decrement(); - watcher.copyCount--; - if (watcher.copyCountApply > 0) { - game.informPlayers(source.getSourceObject(game).getIdName() + " didn't apply"); - this.discard(); - return false; - } - watcher.copyCountApply = watcher.copyCount; - Player blockController = game.getPlayer(source.getControllerId()); - if (blockController != null) { - game.getCombat().selectBlockers(blockController, source, game); - return true; - } - this.discard(); - return false; - } } class MeleeTriggeredAbility extends DelayedTriggeredAbility { diff --git a/Mage.Sets/src/mage/cards/o/OdricMasterTactician.java b/Mage.Sets/src/mage/cards/o/OdricMasterTactician.java index 4128e6cc6db..cecbd547efe 100644 --- a/Mage.Sets/src/mage/cards/o/OdricMasterTactician.java +++ b/Mage.Sets/src/mage/cards/o/OdricMasterTactician.java @@ -1,19 +1,17 @@ package mage.cards.o; -import java.util.UUID; import mage.MageInt; -import mage.abilities.Ability; import mage.abilities.TriggeredAbilityImpl; -import mage.abilities.effects.ContinuousRuleModifyingEffectImpl; -import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.combat.ChooseBlockersEffect; import mage.abilities.keyword.FirstStrikeAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.*; import mage.game.Game; import mage.game.events.GameEvent; -import mage.players.Player; -import mage.watchers.common.ChooseBlockersRedundancyWatcher; +import mage.watchers.common.ControlCombatRedundancyWatcher; + +import java.util.UUID; /** * @author noxx @@ -49,9 +47,8 @@ public final class OdricMasterTactician extends CardImpl { class OdricMasterTacticianTriggeredAbility extends TriggeredAbilityImpl { public OdricMasterTacticianTriggeredAbility() { - super(Zone.BATTLEFIELD, new OdricMasterTacticianChooseBlockersEffect()); - this.addWatcher(new ChooseBlockersRedundancyWatcher()); - this.addEffect(new ChooseBlockersRedundancyWatcherIncrementEffect()); + super(Zone.BATTLEFIELD, new ChooseBlockersEffect(Duration.EndOfCombat)); + this.addWatcher(new ControlCombatRedundancyWatcher()); } public OdricMasterTacticianTriggeredAbility(final OdricMasterTacticianTriggeredAbility ability) { @@ -72,80 +69,4 @@ class OdricMasterTacticianTriggeredAbility extends TriggeredAbilityImpl { public boolean checkTrigger(GameEvent event, Game game) { return game.getCombat().getAttackers().size() >= 4 && game.getCombat().getAttackers().contains(this.sourceId); } - - private class ChooseBlockersRedundancyWatcherIncrementEffect extends OneShotEffect { - - ChooseBlockersRedundancyWatcherIncrementEffect() { - super(Outcome.Neutral); - } - - ChooseBlockersRedundancyWatcherIncrementEffect(final ChooseBlockersRedundancyWatcherIncrementEffect effect) { - super(effect); - } - - @Override - public boolean apply(Game game, Ability source) { - ChooseBlockersRedundancyWatcher watcher = game.getState().getWatcher(ChooseBlockersRedundancyWatcher.class); - if (watcher != null) { - watcher.increment(); - return true; - } - return false; - } - - @Override - public ChooseBlockersRedundancyWatcherIncrementEffect copy() { - return new ChooseBlockersRedundancyWatcherIncrementEffect(this); - } - } -} - -class OdricMasterTacticianChooseBlockersEffect extends ContinuousRuleModifyingEffectImpl { - - public OdricMasterTacticianChooseBlockersEffect() { - super(Duration.EndOfCombat, Outcome.Benefit, false, false); - staticText = "Whenever {this} and at least three other creatures attack, you choose which creatures block this combat and how those creatures block"; - } - - public OdricMasterTacticianChooseBlockersEffect(final OdricMasterTacticianChooseBlockersEffect effect) { - super(effect); - } - - @Override - public OdricMasterTacticianChooseBlockersEffect copy() { - return new OdricMasterTacticianChooseBlockersEffect(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) { - ChooseBlockersRedundancyWatcher watcher = game.getState().getWatcher(ChooseBlockersRedundancyWatcher.class); - if (watcher == null) { - return false; - } - watcher.decrement(); - watcher.copyCount--; - if (watcher.copyCountApply > 0) { - game.informPlayers(source.getSourceObject(game).getIdName() + " didn't apply"); - this.discard(); - return false; - } - watcher.copyCountApply = watcher.copyCount; - Player blockController = game.getPlayer(source.getControllerId()); - if (blockController != null) { - game.getCombat().selectBlockers(blockController, source, game); - return true; - } - this.discard(); - return false; - } } diff --git a/Mage.Sets/src/mage/cards/r/Revivify.java b/Mage.Sets/src/mage/cards/r/Revivify.java index 690999b9807..7fbf9505243 100644 --- a/Mage.Sets/src/mage/cards/r/Revivify.java +++ b/Mage.Sets/src/mage/cards/r/Revivify.java @@ -41,7 +41,7 @@ public final class Revivify extends CardImpl { // Roll a d20 and add the number of creature cards in your graveyard that were put there from the battlefield this turn. RollDieWithResultTableEffect effect = new RollDieWithResultTableEffect( 20, "roll a d20 and add the number of creature cards " + - "in your graveyard that were put there from the battlefield this turn", xValue + "in your graveyard that were put there from the battlefield this turn", xValue, 0 ); this.getSpellAbility().addEffect(effect); this.getSpellAbility().addWatcher(new CardsPutIntoGraveyardWatcher()); diff --git a/Mage.Sets/src/mage/sets/ForgottenRealmsCommander.java b/Mage.Sets/src/mage/sets/ForgottenRealmsCommander.java index d75528bf51b..df24b0f0bb4 100644 --- a/Mage.Sets/src/mage/sets/ForgottenRealmsCommander.java +++ b/Mage.Sets/src/mage/sets/ForgottenRealmsCommander.java @@ -42,6 +42,7 @@ public final class ForgottenRealmsCommander extends ExpansionSet { cards.add(new SetCardInfo("Bedevil", 179, Rarity.RARE, mage.cards.b.Bedevil.class)); cards.add(new SetCardInfo("Behemoth Sledge", 180, Rarity.UNCOMMON, mage.cards.b.BehemothSledge.class)); cards.add(new SetCardInfo("Belt of Giant Strength", 38, Rarity.RARE, mage.cards.b.BeltOfGiantStrength.class)); + cards.add(new SetCardInfo("Berserker's Frenzy", 29, Rarity.RARE, mage.cards.b.BerserkersFrenzy.class)); cards.add(new SetCardInfo("Bituminous Blast", 181, Rarity.UNCOMMON, mage.cards.b.BituminousBlast.class)); cards.add(new SetCardInfo("Bogardan Hellkite", 115, Rarity.MYTHIC, mage.cards.b.BogardanHellkite.class)); cards.add(new SetCardInfo("Bojuka Bog", 226, Rarity.COMMON, mage.cards.b.BojukaBog.class)); diff --git a/Mage/src/main/java/mage/abilities/effects/common/RollDieWithResultTableEffect.java b/Mage/src/main/java/mage/abilities/effects/common/RollDieWithResultTableEffect.java index b198f7daf85..1e65c1f8d08 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/RollDieWithResultTableEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/RollDieWithResultTableEffect.java @@ -27,6 +27,7 @@ public class RollDieWithResultTableEffect extends OneShotEffect { private final String prefixText; private final List resultsTable = new ArrayList<>(); private final DynamicValue modifier; + private final int toIgnore; public RollDieWithResultTableEffect() { this(20); @@ -37,14 +38,15 @@ public class RollDieWithResultTableEffect extends OneShotEffect { } public RollDieWithResultTableEffect(int sides, String prefixText) { - this(sides, prefixText, StaticValue.get(0)); + this(sides, prefixText, StaticValue.get(0), 0); } - public RollDieWithResultTableEffect(int sides, String prefixText, DynamicValue modifier) { + public RollDieWithResultTableEffect(int sides, String prefixText, DynamicValue modifier, int toIgnore) { super(Outcome.Benefit); this.sides = sides; this.prefixText = prefixText; this.modifier = modifier; + this.toIgnore = toIgnore; } protected RollDieWithResultTableEffect(final RollDieWithResultTableEffect effect) { @@ -55,6 +57,7 @@ public class RollDieWithResultTableEffect extends OneShotEffect { this.resultsTable.add(tableEntry.copy()); } this.modifier = effect.modifier.copy(); + this.toIgnore = effect.toIgnore; } @Override @@ -68,7 +71,9 @@ public class RollDieWithResultTableEffect extends OneShotEffect { if (player == null) { return false; } - int result = player.rollDice(outcome, source, game, sides) + modifier.calculate(game, source, this); + int result = player.rollDice( + outcome, source, game, sides, 1 + toIgnore, toIgnore + ).get(0) + modifier.calculate(game, source, this); this.applyResult(result, game, source); return true; } diff --git a/Mage/src/main/java/mage/abilities/effects/common/combat/ChooseBlockersEffect.java b/Mage/src/main/java/mage/abilities/effects/common/combat/ChooseBlockersEffect.java new file mode 100644 index 00000000000..07d1dfee53b --- /dev/null +++ b/Mage/src/main/java/mage/abilities/effects/common/combat/ChooseBlockersEffect.java @@ -0,0 +1,80 @@ +package mage.abilities.effects.common.combat; + +import mage.abilities.Ability; +import mage.abilities.Mode; +import mage.abilities.effects.ContinuousRuleModifyingEffectImpl; +import mage.constants.Duration; +import mage.constants.Outcome; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.players.Player; +import mage.watchers.common.ControlCombatRedundancyWatcher; + +/** + * @author L_J, TheElk801 + */ +public class ChooseBlockersEffect extends ContinuousRuleModifyingEffectImpl { + + public ChooseBlockersEffect(Duration duration) { + super(duration, Outcome.Benefit, false, false); + } + + private ChooseBlockersEffect(final ChooseBlockersEffect effect) { + super(effect); + } + + @Override + public ChooseBlockersEffect copy() { + return new ChooseBlockersEffect(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 void init(Ability source, Game game) { + super.init(source, game); + ControlCombatRedundancyWatcher.addBlockingController(source.getControllerId(), this.duration, game); + } + + @Override + public boolean applies(GameEvent event, Ability source, Game game) { + if (!ControlCombatRedundancyWatcher.checkBlockingController(source.getControllerId(), game)) { + game.informPlayers(source.getSourceObject(game).getIdName() + " didn't apply"); + return false; + } + Player blockController = game.getPlayer(source.getControllerId()); + if (blockController != null) { + game.getCombat().selectBlockers(blockController, source, game); + return true; + } + return false; + } + + @Override + public String getText(Mode mode) { + if (staticText != null && !staticText.isEmpty()) { + return staticText; + } + StringBuilder sb = new StringBuilder("you choose which creatures block this "); + switch (duration) { + case EndOfTurn: + sb.append("turn"); + break; + case EndOfCombat: + sb.append("combat"); + break; + default: + throw new IllegalArgumentException("duration type not supported"); + } + sb.append(" and how those creatures block"); + return sb.toString(); + } +} diff --git a/Mage/src/main/java/mage/watchers/common/ChooseBlockersRedundancyWatcher.java b/Mage/src/main/java/mage/watchers/common/ChooseBlockersRedundancyWatcher.java deleted file mode 100644 index 0b9660699c2..00000000000 --- a/Mage/src/main/java/mage/watchers/common/ChooseBlockersRedundancyWatcher.java +++ /dev/null @@ -1,42 +0,0 @@ -package mage.watchers.common; - -import mage.constants.WatcherScope; -import mage.game.Game; -import mage.game.events.GameEvent; -import mage.watchers.Watcher; - -/** - * @author L_J - */ - -public class ChooseBlockersRedundancyWatcher extends Watcher { // workaround for solving timestamp issues regarding "you choose which creatures block and how those creatures block" effects - - public int copyCount = 0; - public int copyCountApply = 0; - - public ChooseBlockersRedundancyWatcher() { - super(WatcherScope.GAME); - } - - @Override - public void reset() { - super.reset(); - copyCount = 0; - copyCountApply = 0; - } - - @Override - public void watch(GameEvent event, Game game) { - } - - public void increment() { - copyCount++; - copyCountApply = copyCount; - } - - public void decrement() { - if (copyCountApply > 0) { - copyCountApply--; - } - } -} diff --git a/Mage/src/main/java/mage/watchers/common/ControlCombatRedundancyWatcher.java b/Mage/src/main/java/mage/watchers/common/ControlCombatRedundancyWatcher.java new file mode 100644 index 00000000000..d7f75a965c1 --- /dev/null +++ b/Mage/src/main/java/mage/watchers/common/ControlCombatRedundancyWatcher.java @@ -0,0 +1,78 @@ +package mage.watchers.common; + +import mage.constants.Duration; +import mage.constants.WatcherScope; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.watchers.Watcher; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +/** + * @author L_J + */ +public class ControlCombatRedundancyWatcher extends Watcher { // workaround for solving timestamp issues regarding "you choose which creatures block and how those creatures block" effects + + private static final class PlayerDuration { + + private final Duration duration; + private final UUID playerId; + + private PlayerDuration(Duration duration, UUID playerId) { + this.duration = duration; + this.playerId = playerId; + } + + private boolean isCombat() { + return duration == Duration.EndOfCombat; + } + + private boolean isPlayer(UUID playerId) { + return playerId.equals(this.playerId); + } + } + + private final List attackingControllers = new ArrayList<>(); + private final List blockingControllers = new ArrayList<>(); + + public ControlCombatRedundancyWatcher() { + super(WatcherScope.GAME); + } + + @Override + public void reset() { + super.reset(); + attackingControllers.clear(); + blockingControllers.clear(); + } + + @Override + public void watch(GameEvent event, Game game) { + if (event.getType() == GameEvent.EventType.END_COMBAT_STEP_POST) { + attackingControllers.removeIf(PlayerDuration::isCombat); + blockingControllers.removeIf(PlayerDuration::isCombat); + } + } + + public static void addAttackingController(UUID playerId, Duration duration, Game game) { + ControlCombatRedundancyWatcher watcher = game.getState().getWatcher(ControlCombatRedundancyWatcher.class); + watcher.attackingControllers.add(0, new PlayerDuration(duration, playerId)); + } + + public static void addBlockingController(UUID playerId, Duration duration, Game game) { + ControlCombatRedundancyWatcher watcher = game.getState().getWatcher(ControlCombatRedundancyWatcher.class); + watcher.blockingControllers.add(0, new PlayerDuration(duration, playerId)); + } + + public static boolean checkAttackingController(UUID playerId, Game game) { + ControlCombatRedundancyWatcher watcher = game.getState().getWatcher(ControlCombatRedundancyWatcher.class); + return !watcher.attackingControllers.isEmpty() && watcher.attackingControllers.get(0).isPlayer(playerId); + } + + public static boolean checkBlockingController(UUID playerId, Game game) { + ControlCombatRedundancyWatcher watcher = game.getState().getWatcher(ControlCombatRedundancyWatcher.class); + return !watcher.blockingControllers.isEmpty() && watcher.blockingControllers.get(0).isPlayer(playerId); + } +}